From 8e461d7040c175f1b99ca677593dec86cfd20742 Mon Sep 17 00:00:00 2001 From: Palash Shah <35114859+Palashio@users.noreply.github.com> Date: Wed, 6 Aug 2025 19:38:19 -0400 Subject: [PATCH] feat: setup internal open-swe bench (#697) * feat: evals script * swe bench * feat: setup initial internal_bench * fix: move internal bench * update: modify types * Remove scripts/ folder from tracking and add to gitignore * fix: update linter * update: modify the formatting * format: change langbench * fix: update yarn lock * fix: update formatting * fix: update lint * fix: remove file * Update apps/open-swe/package.json Co-authored-by: Brace Sproul * fix: move to dev dependency * fix: update the format * fix: update paths * update: remove function * update: get pre merge commit * update: formatting * update: formatting * update: revert package json * update: just store the pre merge commit:wq * update: remove function * cr --------- Co-authored-by: Brace Sproul --- apps/open-swe/.gitignore | 6 +- apps/open-swe/evals/evaluator.ts | 81 +---- apps/open-swe/langbench/runEvals.eval.ts | 128 ++++++++ .../langbench/static/langgraph_prs.json | 287 ++++++++++++++++++ apps/open-swe/langbench/types.ts | 26 ++ apps/open-swe/package.json | 9 +- apps/open-swe/src/utils/env-setup.ts | 93 ++++++ yarn.lock | 18 ++ 8 files changed, 568 insertions(+), 80 deletions(-) create mode 100644 apps/open-swe/langbench/runEvals.eval.ts create mode 100644 apps/open-swe/langbench/static/langgraph_prs.json create mode 100644 apps/open-swe/langbench/types.ts create mode 100644 apps/open-swe/src/utils/env-setup.ts diff --git a/apps/open-swe/.gitignore b/apps/open-swe/.gitignore index 494dcfd9..f8ae9df0 100644 --- a/apps/open-swe/.gitignore +++ b/apps/open-swe/.gitignore @@ -25,4 +25,8 @@ dist-ssr # LangGraph API .langgraph_api -.env \ No newline at end of file +.env + +evals/dataset/scripts/* +langbench/scripts/* +scripts/ diff --git a/apps/open-swe/evals/evaluator.ts b/apps/open-swe/evals/evaluator.ts index 5576967a..cfc0c1ce 100644 --- a/apps/open-swe/evals/evaluator.ts +++ b/apps/open-swe/evals/evaluator.ts @@ -9,87 +9,12 @@ import { cloneRepo } from "../src/utils/github/git.js"; import { getRepoAbsolutePath } from "@open-swe/shared/git"; import { SimpleEvaluationResult } from "langsmith/vitest"; import { runRuffLint, runMyPyTypeCheck } from "./tests.js"; +import { setupEnv, ENV_CONSTANTS } from "../src/utils/env-setup.js"; const logger = createLogger(LogLevel.INFO, "Evaluator "); -const VENV_PATH = ".venv"; -const RUN_PYTHON_IN_VENV = `${VENV_PATH}/bin/python`; -const RUN_PIP_IN_VENV = `${VENV_PATH}/bin/pip`; - -/** - * Setup Python environment with requirements.txt + ruff + mypy - */ -async function setupEnv( - sandbox: Sandbox, - absoluteRepoDir: string, -): Promise { - logger.info("Setting up Python environment..."); - - const createVenvCommand = "python -m venv .venv"; - const createVenvRes = await sandbox.process.executeCommand( - createVenvCommand, - absoluteRepoDir, - undefined, - TIMEOUT_SEC, - ); - if (createVenvRes.exitCode !== 0) { - logger.error("Failed to create virtual environment", { - createVenvCommand, - createVenvRes, - }); - return false; - } - - const upgradePipRes = await sandbox.process.executeCommand( - `${RUN_PIP_IN_VENV} install --upgrade pip`, - absoluteRepoDir, - undefined, - TIMEOUT_SEC, - ); - if (upgradePipRes.exitCode !== 0) { - logger.warn("Failed to upgrade pip, continuing anyway", { upgradePipRes }); - } - - const requirementsExistRes = await sandbox.process.executeCommand( - "test -f requirements.txt", - absoluteRepoDir, - undefined, - TIMEOUT_SEC, - ); - - if (requirementsExistRes.exitCode === 0) { - logger.info("Found requirements.txt, installing..."); - const installReqRes = await sandbox.process.executeCommand( - `${RUN_PIP_IN_VENV} install -r requirements.txt`, - absoluteRepoDir, - undefined, - TIMEOUT_SEC * 3, - ); - if (installReqRes.exitCode !== 0) { - logger.warn("Failed to install requirements.txt, continuing anyway", { - installReqRes, - }); - } - } else { - logger.info("No requirements.txt found, skipping repository dependencies"); - } - - const installAnalysisToolsRes = await sandbox.process.executeCommand( - `${RUN_PIP_IN_VENV} install ruff mypy`, - absoluteRepoDir, - undefined, - TIMEOUT_SEC, - ); - if (installAnalysisToolsRes.exitCode !== 0) { - logger.error("Failed to install ruff and mypy", { - installAnalysisToolsRes, - }); - return false; - } - - logger.info("Environment setup completed successfully"); - return true; -} +// Use shared constants from env-setup utility +const { RUN_PYTHON_IN_VENV } = ENV_CONSTANTS; /** * Runs ruff and mypy analysis on all Python files in the repository diff --git a/apps/open-swe/langbench/runEvals.eval.ts b/apps/open-swe/langbench/runEvals.eval.ts new file mode 100644 index 00000000..86780918 --- /dev/null +++ b/apps/open-swe/langbench/runEvals.eval.ts @@ -0,0 +1,128 @@ +import * as ls from "langsmith/vitest"; +import dotenv from "dotenv"; +import { Daytona, Sandbox } from "@daytonaio/sdk"; +import { createLogger, LogLevel } from "../src/utils/logger.js"; +import { DEFAULT_SANDBOX_CREATE_PARAMS } from "../src/constants.js"; +import { readFileSync } from "fs"; +import { cloneRepo } from "../src/utils/github/git.js"; +import { TargetRepository } from "@open-swe/shared/open-swe/types"; +import { getRepoAbsolutePath } from "@open-swe/shared/git"; +import { setupEnv } from "../src/utils/env-setup.js"; +import { PRData, PRProcessResult } from "./types.js"; + +dotenv.config(); +const logger = createLogger(LogLevel.INFO, "PR Processor"); + +// Load PRs data +const prsData: PRData[] = JSON.parse( + readFileSync("langbench/static/langgraph_prs.json", "utf8"), +); + +const DATASET = prsData.map((pr) => ({ inputs: pr })); +const DATASET_NAME = "langgraph-prs"; + +logger.info(`Starting evals over ${DATASET.length} PRs...`); + +/** + * Process a single PR + */ +async function processPR(prData: PRData): Promise { + const result: PRProcessResult = { + pr_number: prData.pr_number, + repo_name: prData.repo_name, + success: false, + evals_found: false, + evals_files: [], + }; + const daytona = new Daytona({ + organizationId: process.env.DAYTONA_ORGANIZATION_ID, + }); + let sandbox: Sandbox | undefined; + + try { + logger.info(`Processing PR #${prData.pr_number}: ${prData.title}`); + + // Create sandbox + sandbox = await daytona.create(DEFAULT_SANDBOX_CREATE_PARAMS); + result.workspace_id = sandbox.id; + + logger.info(`Created sandbox: ${sandbox.id}`); + + // Use the hardcoded pre-merge commit SHA from the dataset + const preMergeCommit = prData.pre_merge_commit_sha; + logger.info(`Using pre-merge commit: ${preMergeCommit}`); + result.pre_merge_sha = preMergeCommit; + + const targetRepository: TargetRepository = { + owner: prData.repo_owner, + repo: prData.repo_name, + branch: undefined, + baseCommit: preMergeCommit, + }; + const repoDir = getRepoAbsolutePath(targetRepository); + + // Clone and checkout the repository at the pre-merge commit + const githubToken = process.env.GITHUB_PAT; + if (!githubToken) { + throw new Error("GITHUB_PAT environment variable is required"); + } + + await cloneRepo(sandbox, targetRepository, { + githubInstallationToken: githubToken, + }); + + // Setup Python environment + logger.info("Setting up Python environment..."); + const envSetupSuccess = await setupEnv(sandbox, repoDir); + if (!envSetupSuccess) { + logger.warn("Failed to setup Python environment, continuing anyway"); + } + + result.success = true; + logger.info(`Successfully processed PR #${prData.pr_number}`); + } catch (error) { + result.error = error instanceof Error ? error.message : String(error); + logger.error(`Failed to process PR #${prData.pr_number}:`, { error }); + } finally { + // Cleanup sandbox + if (sandbox) { + try { + await sandbox.delete(); + logger.info(`Deleted sandbox: ${sandbox.id}`); + } catch (cleanupError) { + logger.warn(`Failed to cleanup sandbox ${sandbox.id}:`, { + cleanupError, + }); + } + } + } + + return result; +} + +ls.describe(DATASET_NAME, () => { + ls.test.each(DATASET)( + "Can process PR successfully", + async ({ inputs: prData }) => { + logger.info(`Processing PR #${prData.pr_number}: ${prData.title}`); + + const result = await processPR(prData); + + // Log results for visibility + logger.info(`PR #${prData.pr_number} processing completed`, { + success: result.success, + evals_found: result.evals_found, + evals_files_count: result.evals_files.length, + error: result.error, + workspace_id: result.workspace_id, + pre_merge_sha: result.pre_merge_sha, + }); + + // Assert that processing was successful + if (!result.success) { + throw new Error(`PR processing failed: ${result.error}`); + } + }, + 300_000, // 5 minute timeout per PR + ); +}); diff --git a/apps/open-swe/langbench/static/langgraph_prs.json b/apps/open-swe/langbench/static/langgraph_prs.json new file mode 100644 index 00000000..f75b294e --- /dev/null +++ b/apps/open-swe/langbench/static/langgraph_prs.json @@ -0,0 +1,287 @@ +[ + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/5243", + "html_url": "https://github.com/langchain-ai/langgraph/pull/5243", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/5243.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/5243.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 5243, + "merge_commit_sha": "08372635424fd9e2957d763cb0f2f466da552141", + "title": "feat(langgraph): new context api (replacing `config['configurable']` and `config_schema`)", + "body": "## Overview\r\n\r\nThis PR introduces a new API that provides a cleaner, more type-safe way to pass runtime context to LangGraph nodes/tasks. It replaces the current pattern of using `config['configurable']` and `config_schema` with a dedicated `context` parameter and wrapper `Runtime` object.\r\n\r\n## What's Changed\r\n\r\n### Before/After: Basic Context Usage\r\n\r\n### Before (Old Pattern)\r\n```python\r\nfrom langchain_core.runnables import RunnableConfig\r\n\r\ndef node(state: State, config: RunnableConfig):\r\n user_id = config.get(\"configurable\", {}).get(\"user_id\")\r\n return {\"result\": f\"Hello {user_id}\"}\r\n\r\ngraph.invoke(input_data, config={\"configurable\": {\"user_id\": \"123\"}})\r\n```\r\n\r\n### After (New Pattern)\r\n```python\r\nfrom dataclasses import dataclass\r\nfrom langgraph.runtime import Runtime\r\n\r\n@dataclass\r\nclass ContextSchema:\r\n user_id: str\r\n\r\ndef node(state: State, runtime: Runtime[ContextSchema]):\r\n user_id = runtime.context.user_id\r\n return {\"result\": f\"Hello {user_id}\"}\r\n\r\ngraph.invoke(input_data, context={\"user_id\": \"123\"})\r\n```\r\n\r\nOR, you can use the `get_runtime` method:\r\n```python\r\nfrom langgraph.runtime import get_runtime\r\n\r\ndef node(state: State):\r\n user_id = get_runtime(ContextSchema).context.user_id\r\n return {\"result\": f\"Hello {user_id}\"}\r\n```\r\n\r\n
\r\nBefore/After: Store and Stream Writer Access\r\n\r\n### Before (Old pattern)\r\n```py\r\nfrom langgraph.store.base import BaseStore\r\nfrom langchain_core.runnables import RunnableConfig\r\n\r\ndef update_memory(state: MessagesState, config: RunnableConfig, *, store: BaseStore):\r\n user_id = config.get(\"configurable\", {}).get(\"user_id\")\r\n namespace = (user_id, \"memories\")\r\n memory_id = str(uuid.uuid4())\r\n store.put(namespace, memory_id, {\"memory\": memory})\r\n```\r\n\r\n### After (new pattern)\r\n```py\r\nfrom langgraph.runtime import Runtime\r\n\r\ndef update_memory(state: MessagesState, runtime: Runtime[ContextSchema]):\r\n user_id = runtime.context.user_id\r\n namespace = (user_id, \"memories\")\r\n memory_id = str(uuid.uuid4())\r\n runtime.store.put(namespace, memory_id, {\"memory\": memory})\r\n```\r\n
\r\n\r\n## Key Benefits\r\n- **Type Safety**: Type checked `context` input to `invoke` / `stream`, plus typed access to `Runtime` attributes\r\n- **Cleaner API**: Direct `context` parameter instead of nested `config['configurable']`\r\n- **Better DX**: IDE autocomplete for context fields and `Runtime` attributes\r\n- **Unified Runtime**: Single `Runtime` object provides access to context, store, and stream writer, with room for expanding to streamlined config/checkpoint information in the near future.\r\n\r\n## Breaking Changes & Migration\r\n\r\n### Deprecated APIs\r\n- `StateGraph(..., config_schema=X)` -> `StateGraph(..., context_schema=X)`\r\n- `Pregel.config_schema` → `Pregel.get_context_jsonschema()` - this is largely meant to be external and we don't anticipate this affecting many users\r\n\r\n### Migration Details\r\n- Maintains backward compatibility with existing `config['configurable']` usage\r\n- Deprecation warnings guide users to the new API\r\n\r\n## Future Work\r\n\r\n- [ ] Deprecate injection pattern for `store`, `stream_writer`, maybe `previous`, update docs to recommend popping from runtime.\r\n- [ ] Support `Runtime` injection for tools, right now only `get_runtime` is supported\r\n- [ ] LangGraph style guide with recommended best practices\r\n\r\nEventually, I think we should move away from storing and popping things from `config[\"configurable\"]`, which can be a fully internal refactor. Though I would love to do this pre v1, it should largely be internal, so can be done afterwards. Config changes should be in a different PR than this one to keep things reasonably scoped. This PR is already pretty big. Lots of plumbing.\r\n\r\n## Related Issues\r\nCloses #5023\r\n", + "created_at": "2025-06-28T01:04:08Z", + "merged_at": "2025-07-15T13:20:20Z", + "pre_merge_commit_sha": "e0bf4a7bc35d9bb9b9c52a0c652446ff9c9734ba" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/4374", + "html_url": "https://github.com/langchain-ai/langgraph/pull/4374", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/4374.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/4374.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 4374, + "merge_commit_sha": "4eb124e83de865d9bbff83212020e14ddea54465", + "title": "[breaking]: Improve interrupt behavior when `stream_mode='values'`", + "body": "This PR does a few things:\r\n1. Surfaces interrupts when `stream_mode='values'` (particularly relevant for `invoke`, where this is the default behavior) \r\n2. Adds an `interrupt_id` property to the `Interrupt` dataclass so that interrupts can effectively be mapped to resumes\r\n3. Minor docs updates to reflect the new pattern (no need for a special section on interrupts with `invoke` and `ainvoke`)\r\n\r\n* In a different PR (the one with the multiple resume values), as it's more relevant there: add an `interrupts` property to `StateSnapshot` so that `interrupts` can easily be iterated over if users are attempting to map interrupts to resumes.\r\n\r\nI **don't** recommend we release this until we have multi-resumes working.\r\n\r\n## Example\r\n\r\nWe have the following setup where we're sending multiple prompts to the child graph, which uses `interrupt`:\r\n\r\n```py\r\ndef child_graph(state):\r\n human_input = interrupt(state[\"prompt\"])\r\n\r\n return {\r\n \"human_inputs\": [human_input],\r\n }\r\n```\r\n\r\n\"Screenshot\r\n\r\nOld behavior:\r\n\r\n```py\r\ninitial_input = {\"prompts\": [\"a\", \"b\"]}\r\n\r\nprint(parent_graph.invoke(input=initial_input,config=thread_config,stream_mode=\"values\"))\r\n#> {'prompts': ['a', 'b'], 'human_inputs': []}\r\n\r\nprint(parent_graph.invoke(Command(resume=\"hello 1\"),config=thread_config,stream_mode=\"values\"))\r\n#> {'prompts': ['a', 'b'], 'human_inputs': ['hello 1']}\r\n\r\nprint(parent_graph.invoke(Command(resume=\"hello 2\"),config=thread_config,stream_mode=\"values\"))\r\n#> {'prompts': ['a', 'b'], 'human_inputs': ['hello 1', 'hello 2']}\r\n```\r\n\r\nNew behavior:\r\n\r\n```py\r\ninitial_input = {\"prompts\": [\"a\", \"b\"]}\r\n\r\nprint(parent_graph.invoke(input=initial_input,config=thread_config,stream_mode=\"values\"))\r\n\"\"\"\r\n{\r\n \"prompts\": [\"a\", \"b\"],\r\n \"human_inputs\": [],\r\n \"__interrupt__\": [\r\n Interrupt(\r\n value=\"a\",\r\n resumable=True,\r\n ns=[\"child_graph:38d43a18-a5e7-8ab2-ca83-9d80f6e9ca83\"]\r\n ),\r\n Interrupt(\r\n value=\"b\",\r\n resumable=True,\r\n ns=[\"child_graph:dad810e8-738e-9f90-41cd-30c0091eb79b\"]\r\n )\r\n ]\r\n}\r\n\"\"\"\r\n\r\nprint(parent_graph.invoke(Command(resume=\"hello 1\"),config=thread_config,stream_mode=\"values\"))\r\n\"\"\"\r\n{\r\n \"prompts\": [\"a\", \"b\"],\r\n \"human_inputs\": [\"hello 1\"],\r\n \"__interrupt__\": [\r\n Interrupt(\r\n value=\"b\",\r\n resumable=True,\r\n ns=[\"child_graph:dad810e8-738e-9f90-41cd-30c0091eb79b\"]\r\n )\r\n ]\r\n}\r\n\"\"\"\r\n\r\nprint(parent_graph.invoke(Command(resume=\"hello 2\"),config=thread_config,stream_mode=\"values\"))\r\n#> {'prompts': ['a', 'b'], 'human_inputs': ['hello 1', 'hello 2']}\r\n```", + "created_at": "2025-04-22T17:17:21Z", + "merged_at": "2025-04-24T15:21:28Z", + "pre_merge_commit_sha": "b81c21f311fd131d5969c33d238be2eeed5cc522" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/3126", + "html_url": "https://github.com/langchain-ai/langgraph/pull/3126", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/3126.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/3126.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 3126, + "merge_commit_sha": "a37c4d6f4928a3e1d91f2061fc6af142b17e0408", + "title": "langgraph[patch]: allow ToolNode to accept ToolCalls", + "body": "Alternative to https://github.com/langchain-ai/langgraph/pull/3124\r\n\r\nCurrently if a tool interrupts, the entire tool node executes again after resuming. So tools can get executed twice if parallel tool calls are generated. Here we allow ToolNode to accept tool calls, so we can use the `Send` API to distribute the tool calls to multiple instances of the tool node.\r\n\r\n```python\r\nfrom langchain_anthropic import ChatAnthropic\r\nfrom langchain_core.tools import tool\r\nfrom langgraph.checkpoint.memory import MemorySaver\r\nfrom langgraph.prebuilt import create_react_agent\r\nfrom langgraph.types import Command, Send, interrupt\r\n\r\n\r\n@tool\r\ndef human_assistance(query: str) -> str:\r\n \"\"\"Request assistance from a human.\"\"\"\r\n human_response = interrupt({\"query\": query})\r\n return human_response[\"data\"]\r\n\r\n\r\n@tool\r\ndef get_weather(location: str) -> str:\r\n \"\"\"Use this tool to get the weather.\"\"\"\r\n return \"It's sunny!\"\r\n\r\n\r\ntools = [get_weather, human_assistance]\r\nllm = ChatAnthropic(model=\"claude-3-5-sonnet-20240620\")\r\n\r\nagent = create_react_agent(\r\n llm,\r\n tools,\r\n checkpointer=MemorySaver(),\r\n tool_call_parallelism=\"parallel_tool_nodes\",\r\n)\r\n\r\n\r\nuser_input = (\r\n \"Could you please (1) request assistance for building an AI agent \"\r\n \"from a human, and (2) search for the weather in Boston, MA? \"\r\n \"Generate two tool calls at once.\"\r\n)\r\n\r\nconfig = {\"configurable\": {\"thread_id\": \"1\"}}\r\n\r\nfor event in agent.stream(\r\n {\"messages\": [{\"role\": \"user\", \"content\": user_input}]},\r\n config,\r\n stream_mode=\"values\",\r\n):\r\n event[\"messages\"][-1].pretty_print()\r\n```\r\n```\r\n...\r\n```\r\n```python\r\nhuman_response = \"You should check out LangGraph to build your agent.\"\r\nhuman_command = Command(resume={\"data\": human_response})\r\n\r\nfor event in agent.stream(human_command, config, stream_mode=\"values\"):\r\n event[\"messages\"][-1].pretty_print()\r\n```", + "created_at": "2025-01-21T17:58:50Z", + "merged_at": "2025-01-31T17:20:59Z", + "pre_merge_commit_sha": "4b3e07b67aa5a992531cab169286c3cda0c38a0a" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/3095", + "html_url": "https://github.com/langchain-ai/langgraph/pull/3095", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/3095.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/3095.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 3095, + "merge_commit_sha": "444faec6e6c635b6ee343da806b2bbd4a7bbce6b", + "title": "Fix two issues with task/stream timing", + "body": "- both issues are related to the fact that waiters for futures are notified of completion before \"done\" callbacks are called\r\n- 1st issue manifested as interrupt stream event being emitted before the result of a task that logically finished first (it's in the line above in body of the entrypoint function) -> this is solved by always returning to use code a fresh future chained on the original future, because chaining is done via done callbacks (therefore the chained future will only resolve after done callbacks of the original feature are called)\r\n- 2nd issue mainfested as sometimes (very rarely) the last stream event not being printed before stream() finishes. this is solved by ensuring we only return out of PregelRunner.tick() once all \"done\" callbacks are called, previously we were approximating this through use of asyncio.sleep(0) / time.sleep(0). The new solution instead waits on a threading/asyncio.Event which will only be set by the last \"done\" callback to fire\r\n- this PR also disables incomplete support for calling sync tasks from async entrypoints", + "created_at": "2025-01-17T23:35:26Z", + "merged_at": "2025-01-17T23:44:52Z", + "pre_merge_commit_sha": "e4a5c8fd28ceca30171073aebd64b802699c54ba" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/2848", + "html_url": "https://github.com/langchain-ai/langgraph/pull/2848", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/2848.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/2848.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 2848, + "merge_commit_sha": "10d46acc60d19db426b6508b8b81de96aa1bab6d", + "title": "langgraph: add structured output to create_react_agent", + "body": "```python\r\nclass WeatherResponse(BaseModel):\r\n \"\"\"Respond to the user with this\"\"\"\r\n\r\n temperature: float = Field(description=\"The temperature in fahrenheit\")\r\n wind_direction: str = Field(\r\n description=\"The direction of the wind in abbreviated form\"\r\n )\r\n wind_speed: float = Field(description=\"The speed of the wind in mph\")\r\n\r\n@tool\r\ndef get_weather(city: Literal[\"nyc\", \"sf\"]):\r\n \"\"\"Use this to get weather information.\"\"\"\r\n if city == \"nyc\":\r\n return \"It is cloudy in NYC, with 5 mph winds in the North-East direction and a temperature of 70 degrees\"\r\n elif city == \"sf\":\r\n return \"It is 75 degrees and sunny in SF, with 3 mph winds in the South-East direction\"\r\n else:\r\n raise AssertionError(\"Unknown city\")\r\n\r\nmodel = ChatOpenAI()\r\ntools = [get_weather]\r\nagent_with_structured_output = create_react_agent(model, tools, response_format=WeatherResponse)\r\nagent_with_structured_output.invoke({\"messages\": [(\"user\", \"what's the weather in nyc?\")]})\r\n```\r\n\r\n```pycon\r\n{\r\n 'messages': [...],\r\n 'structured_response': WeatherResponse(temperature=70.0, wind_directon='NE', wind_speed=5.0)\r\n}\r\n```", + "created_at": "2024-12-20T20:31:20Z", + "merged_at": "2025-01-10T16:06:59Z", + "pre_merge_commit_sha": "35c3ba0104804bee045675ee8bce754deccacfc2" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/1004", + "html_url": "https://github.com/langchain-ai/langgraph/pull/1004", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/1004.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/1004.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 1004, + "merge_commit_sha": "738f725aeacf41d90b0081b3ae79754d1d1823e0", + "title": "Support multiple interruptions after resuming execution", + "body": "We noticed that it is currently not possible to interrupt a graph multiple times.\r\n\r\nOnce the graph resumes execution after an interruption, it just continues executing, ignoring `interrupt_before` and `interrupt_after`.\r\n\r\nThe reason is that after resuming this condition in `_should_interrupt` seems to always return false:\r\n```\r\nany(\r\n checkpoint[\"channel_versions\"].get(chan, null_version)\r\n > seen.get(chan, null_version)\r\n for chan in snapshot_channels\r\n)\r\n```\r\n\r\nIn this PR I added a unit test in `test_interruption.py` to spec the desired behavior. I modified the code to pass the unit test, but since I do not understand what this code does it's probably not the right thing.\r\n\r\nIt would be great to get some guidance on how to fix this properly.\r\n", + "created_at": "2024-07-12T16:42:14Z", + "merged_at": "2024-07-12T19:53:17Z", + "pre_merge_commit_sha": "558a513a1acbc0ae88ae24d6e5cc13325ab00ad1" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/5801", + "html_url": "https://github.com/langchain-ai/langgraph/pull/5801", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/5801.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/5801.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 5801, + "merge_commit_sha": "2920a9dd197e75554720dae3e0c6bebb638fa621", + "title": "fix(langgraph): Tidy up `AgentState`", + "body": "Fixes https://github.com/langchain-ai/langgraph/issues/5784\r\n\r\n* Removes usage of `is_last_step`, no longer needed with `remaining_steps`\r\n* Make `remaining_steps` `NotRequired` so that json schema doesn't suggest need for user input\r\n* Move `PregelScratchpad` to shared utils file to prevent circular import issue (it's used from `channels/managed` and other pregel files).\r\n* Ensures that managed values wrapped in `NotRequired` or `Required` are still recognized!", + "created_at": "2025-08-01T18:31:32Z", + "merged_at": "2025-08-03T11:12:54Z", + "pre_merge_commit_sha": "db8ed4e9e424ed29c8165602f17ee800c671681b" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/5796", + "html_url": "https://github.com/langchain-ai/langgraph/pull/5796", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/5796.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/5796.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 5796, + "merge_commit_sha": "220314b53a960964a82657451b846f3a7cb2f348", + "title": "fix(langgraph): fix up deprecation warnings", + "body": "Fixes https://github.com/langchain-ai/langgraph/issues/5795\r\n\r\n* Must use `category=None` on decorator so that we get type checking support but no dupe warning\r\n* Fixed tuple on `config_type` warning causing false warning", + "created_at": "2025-08-01T14:27:51Z", + "merged_at": "2025-08-01T14:33:46Z", + "pre_merge_commit_sha": "38bbd92e01d8437b70c45355b6005fa40c204844" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/5708", + "html_url": "https://github.com/langchain-ai/langgraph/pull/5708", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/5708.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/5708.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 5708, + "merge_commit_sha": "7436777e7d5ebdea1a7787755b71c75faca9885f", + "title": "fix(langgraph): enforce config injection even when optional", + "body": "Fixes https://github.com/langchain-ai/langgraph/issues/5698\r\n\r\nI would like to do a more general refactor of this logic at some point as well using typing introspection utilities. Claude code first pass: https://github.com/langchain-ai/langgraph/pull/5709", + "created_at": "2025-07-29T19:14:10Z", + "merged_at": "2025-07-29T20:11:37Z", + "pre_merge_commit_sha": "479373bd81f538b81362ae8bea9d1b6923e1f60e" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/4983", + "html_url": "https://github.com/langchain-ai/langgraph/pull/4983", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/4983.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/4983.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 4983, + "merge_commit_sha": "b7354521537175aed60c6b8bacac22049ada8ca6", + "title": "deprecate `input` and `output` in favor of `input_schema` and `output_schema`", + "body": "* rename `input` -> `input_schema`\r\n* rename `output` -> `output_schema`\r\n* make graphs generic on `OutputT` to prep for future type checking\r\n\r\nAll renaming operations are backwards compatible in that we populate old input / output into their respective new schemas!", + "created_at": "2025-06-06T18:53:54Z", + "merged_at": "2025-06-06T23:44:56Z", + "pre_merge_commit_sha": "5920d8aa92fb8a76c7629a65acac5480387de0a5" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/3889", + "html_url": "https://github.com/langchain-ai/langgraph/pull/3889", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/3889.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/3889.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 3889, + "merge_commit_sha": "fc8e6ec64f84f036bbfb8d1da04bfe8a03051bdb", + "title": "When using global resume value, ensure subgraphs consume it", + "body": "- Previously the global resume value was passed to subgraphs without being consumed\r\n- This would result in two parallel subgraph calls being able to use the same resume value\r\n- Note this behavior can't be implemented over the wire, that will be fixed in future PR\r\n\r\nCloses #3398 ", + "created_at": "2025-03-18T03:32:30Z", + "merged_at": "2025-03-18T04:26:34Z", + "pre_merge_commit_sha": "dd16ae4ba5243b4f0e4228f9a00aac477146c301" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/3110", + "html_url": "https://github.com/langchain-ai/langgraph/pull/3110", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/3110.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/3110.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 3110, + "merge_commit_sha": "3ec55b008d0f1e802db71635ba7fbb0f8dabab27", + "title": "Fix timing issue where a sync task would finish before the other one was registered in futures dict", + "body": "\r\n\r\n- this was not possible in async where all done callbacks are called in next tick\r\n- in sync case this would manifest as the first task done callback seeing counter == 1 and thus setting event\r\n- the fix is to unset the event whenever a task is scheduled", + "created_at": "2025-01-20T19:41:44Z", + "merged_at": "2025-01-21T18:16:10Z", + "pre_merge_commit_sha": "d48b25420ba7553a7154d3960fb1bf327d497e9d" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/3037", + "html_url": "https://github.com/langchain-ai/langgraph/pull/3037", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/3037.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/3037.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 3037, + "merge_commit_sha": "aab6fdf3f3c5ff6f695335cc0896b32da4b1dc6e", + "title": "Fix Send order after interrupt/resume", + "body": "- order was incorrectly based on task id, instead of the correct task path\r\n- this requires storing task paths on checkpointers\r\n- addition of task_path to put_writes is made backwards compatible by checking signature on call, and treating it as an optional arg", + "created_at": "2025-01-15T02:12:35Z", + "merged_at": "2025-01-15T19:42:08Z", + "pre_merge_commit_sha": "0adbd89d9aaad57e8e4431f308c4372a740e4cbf" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/2393", + "html_url": "https://github.com/langchain-ai/langgraph/pull/2393", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/2393.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/2393.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 2393, + "merge_commit_sha": "29f833b1a77397adab5e13e953f7e9b48f4a0174", + "title": "lib: Add interrupt() function", + "body": "- This works similarly to the input() function from stdlib\r\n- calling it in a node interrupts execution\r\n- invoking the graph with Command(resume=...) will set ... as the return value of interrupt() so that the node can access the \"answer\" to the \"question\"\r\n- This PR also starts the work to control the graph on invoke/stream with Command() input, to be continued in a future PR\r\n\r\n```py\r\n class State(TypedDict):\r\n my_key: Annotated[str, operator.add]\r\n market: str\r\n\r\n async def tool_two_node(s: State) -> State:\r\n if s[\"market\"] == \"DE\":\r\n answer = interrupt(\"Just because...\")\r\n else:\r\n answer = \" all good\"\r\n return {\"my_key\": answer}\r\n\r\n tool_two_graph = StateGraph(State)\r\n tool_two_graph.add_node(\"tool_two\", tool_two_node)\r\n tool_two_graph.add_edge(START, \"tool_two\")\r\n tool_two = tool_two_graph.compile()\r\n\r\n tool_two = tool_two_graph.compile(checkpointer=checkpointer)\r\n\r\n # flow: interrupt -> resume with answer\r\n thread2 = {\"configurable\": {\"thread_id\": \"2\"}}\r\n # stop when about to enter node\r\n assert [\r\n c\r\n async for c in tool_two.astream(\r\n {\"my_key\": \"value ⛰️\", \"market\": \"DE\"}, thread2\r\n )\r\n ] == [\r\n {\"__interrupt__\": [Interrupt(value=\"Just because...\", when=\"during\")]},\r\n ]\r\n # resume with answer\r\n assert [\r\n c async for c in tool_two.astream(Command(resume=\" my answer\"), thread2)\r\n ] == [\r\n {\"tool_two\": {\"my_key\": \" my answer\"}},\r\n ]\r\n```", + "created_at": "2024-11-12T01:45:14Z", + "merged_at": "2024-11-13T21:34:20Z", + "pre_merge_commit_sha": "7a3ea427432dd5e8f4ee101a8c773f3afbc3214c" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/1776", + "html_url": "https://github.com/langchain-ai/langgraph/pull/1776", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/1776.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/1776.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 1776, + "merge_commit_sha": "b03d9ae52c802fef14388a2eb4c2a19fe5550647", + "title": "Add stream_mode=custom", + "body": "- adds the ability for nodes (including in subgraphs) to emit chunks directly to the output stream, emitted chunks can have any type\r\n- when stream_mode=custom isnt requested by the caller emitted chunks are ignored", + "created_at": "2024-09-19T23:46:37Z", + "merged_at": "2024-09-20T16:18:51Z", + "pre_merge_commit_sha": "531890e35a35f9b381d375167a7b104184378153" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/1735", + "html_url": "https://github.com/langchain-ai/langgraph/pull/1735", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/1735.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/1735.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 1735, + "merge_commit_sha": "c4d4d61a43877419ffb51f8077ca6217ffda2a18", + "title": "Stream subgraph output while it executes", + "body": "- previous behavior was to buffer all output from subgraph until it finished, now subgraph steps are emitted as soon as produced, while the subgraph is still running\r\n- this is slightly slower in benchmark scripts, but worth it as it's much \"faster\" in real-world latency", + "created_at": "2024-09-17T01:04:29Z", + "merged_at": "2024-09-17T17:14:26Z", + "pre_merge_commit_sha": "f59435a892e96fb9092e0055a6df2e1dfc5111a9" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/1630", + "html_url": "https://github.com/langchain-ai/langgraph/pull/1630", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/1630.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/1630.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 1630, + "merge_commit_sha": "e3ca7bb3e9d34b09633852f4d08d55f6dcd4364b", + "title": "Implement LangGraph Scheduler for Kafka", + "body": "- Orchestrator and Executor classes to run LangGraph in a distributed fashion using Kafka as a message bus for communication\r\n- Orchestrator and Executor run on-demand when a new message is published to the topic they listen to\r\n- Orchestrator is responsible for running the Pregel algorithm (deciding next tasks to run) and sending messages to the executor topic\r\n- Executor is responsible for executing each task (node), and sending messages to the orchestrator topic when done", + "created_at": "2024-09-06T01:01:06Z", + "merged_at": "2024-09-11T00:31:59Z", + "pre_merge_commit_sha": "34d530d5d83837fa080c1db2d055fe952cfc8488" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/809", + "html_url": "https://github.com/langchain-ai/langgraph/pull/809", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/809.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/809.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 809, + "merge_commit_sha": "8e611b42aa35a7032c81ad2fd264d3192d47c911", + "title": "Fix bug in add_conditional_edges when no path_map is provided", + "body": "When an instance of a callable class is passed as the path arg to add_conditional_edges but no path_map is provided, get_type_hints(path) is called, which raises a TypeError (since get_type_hints only accepts a module, class, method, or function).\r\n\r\nThis patch fixes the error by trying to get type hints from path.\\_\\_call\\_\\_ first, which should work for instances of callable classes.\r\n\r\nTested: Added a test that raises TypeError without the fix in this patch but passes with the fix.", + "created_at": "2024-06-25T21:38:27Z", + "merged_at": "2024-06-26T23:24:59Z", + "pre_merge_commit_sha": "6ae59581643b51751731ae64c609a8bc21779714" + }, + { + "url": "https://api.github.com/repos/langchain-ai/langgraph/pulls/651", + "html_url": "https://github.com/langchain-ai/langgraph/pull/651", + "diff_url": "https://github.com/langchain-ai/langgraph/pull/651.diff", + "patch_url": "https://github.com/langchain-ai/langgraph/pull/651.patch", + "repo_owner": "langchain-ai", + "repo_name": "langgraph", + "pr_number": 651, + "merge_commit_sha": "6e7265a65950af8d152843e41fef2a73be6ab4cb", + "title": "langgraph: add support for deleting messages", + "body": "This change allows users or graph nodes to remove messages by `id` via `langchain_core.messages.RemoveMessage`\r\n\r\nExamples:\r\n\r\n* allow users to delete messages from state by calling\r\n\r\n```python\r\ngraph.update_state(config, values=[RemoveMessage(id=state.values[-1].id)])\r\n```\r\n\r\n* allow nodes to delete messages\r\n\r\n```python\r\ngraph.add_node(\"delete_messages\", lambda state: [RemoveMessage(id=state[-1].id)])\r\n```", + "created_at": "2024-06-12T14:35:51Z", + "merged_at": "2024-07-03T05:43:54Z", + "pre_merge_commit_sha": "5e8aa5d9f2e24b197ffa187c6b7b36602761d1a4" + } +] diff --git a/apps/open-swe/langbench/types.ts b/apps/open-swe/langbench/types.ts new file mode 100644 index 00000000..2bcaea6a --- /dev/null +++ b/apps/open-swe/langbench/types.ts @@ -0,0 +1,26 @@ +export interface PRData { + url: string; + html_url: string; + diff_url: string; + patch_url: string; + repo_owner: string; + repo_name: string; + pr_number: number; + merge_commit_sha: string; + pre_merge_commit_sha: string; + title: string; + body: string; + created_at: string; + merged_at: string; +} + +export interface PRProcessResult { + pr_number: number; + repo_name: string; + workspace_id?: string; + success: boolean; + evals_found: boolean; + evals_files: string[]; + error?: string; + pre_merge_sha?: string; +} diff --git a/apps/open-swe/package.json b/apps/open-swe/package.json index 11aea978..a4341b2b 100644 --- a/apps/open-swe/package.json +++ b/apps/open-swe/package.json @@ -53,6 +53,8 @@ "@jest/globals": "^29.7.0", "@langchain/langgraph-cli": "^0.0.47", "@tsconfig/recommended": "^1.0.8", + "@types/commander": "^2.12.5", + "commander": "^14.0.0", "@types/jest": "^29.5.0", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^22.13.5", @@ -71,5 +73,10 @@ "typescript-eslint": "^8.22.0", "vitest": "^3.2.3" }, - "packageManager": "yarn@3.5.1" + "packageManager": "yarn@3.5.1", + "description": "The core LangGraph agent application that powers Open SWE's autonomous code understanding, planning, and execution capabilities.", + "license": "MIT", + "bugs": { + "url": "https://github.com/langchain-ai/open-swe/issues" + } } diff --git a/apps/open-swe/src/utils/env-setup.ts b/apps/open-swe/src/utils/env-setup.ts new file mode 100644 index 00000000..efc9f15e --- /dev/null +++ b/apps/open-swe/src/utils/env-setup.ts @@ -0,0 +1,93 @@ +import { Sandbox } from "@daytonaio/sdk"; +import { createLogger, LogLevel } from "./logger.js"; +import { TIMEOUT_SEC } from "@open-swe/shared/constants"; + +const logger = createLogger(LogLevel.INFO, "EnvSetup"); + +const VENV_PATH = ".venv"; +const RUN_PYTHON_IN_VENV = `${VENV_PATH}/bin/python`; +const RUN_PIP_IN_VENV = `${VENV_PATH}/bin/pip`; + +/** + * Setup Python environment with requirements.txt + ruff + mypy + */ +export async function setupEnv( + sandbox: Sandbox, + absoluteRepoDir: string, +): Promise { + logger.info("Setting up Python environment..."); + + const createVenvCommand = "python -m venv .venv"; + const createVenvRes = await sandbox.process.executeCommand( + createVenvCommand, + absoluteRepoDir, + undefined, + TIMEOUT_SEC, + ); + if (createVenvRes.exitCode !== 0) { + logger.error("Failed to create virtual environment", { + createVenvCommand, + createVenvRes, + }); + return false; + } + + const upgradePipRes = await sandbox.process.executeCommand( + `${RUN_PIP_IN_VENV} install --upgrade pip`, + absoluteRepoDir, + undefined, + TIMEOUT_SEC, + ); + if (upgradePipRes.exitCode !== 0) { + logger.warn("Failed to upgrade pip, continuing anyway", { upgradePipRes }); + } + + const requirementsExistRes = await sandbox.process.executeCommand( + "test -f requirements.txt", + absoluteRepoDir, + undefined, + TIMEOUT_SEC, + ); + + if (requirementsExistRes.exitCode === 0) { + logger.info("Found requirements.txt, installing..."); + const installReqRes = await sandbox.process.executeCommand( + `${RUN_PIP_IN_VENV} install -r requirements.txt`, + absoluteRepoDir, + undefined, + TIMEOUT_SEC * 3, + ); + if (installReqRes.exitCode !== 0) { + logger.warn("Failed to install requirements.txt, continuing anyway", { + installReqRes, + }); + } + } else { + logger.info("No requirements.txt found, skipping repository dependencies"); + } + + const installAnalysisToolsRes = await sandbox.process.executeCommand( + `${RUN_PIP_IN_VENV} install ruff mypy`, + absoluteRepoDir, + undefined, + TIMEOUT_SEC, + ); + if (installAnalysisToolsRes.exitCode !== 0) { + logger.error("Failed to install ruff and mypy", { + installAnalysisToolsRes, + }); + return false; + } + + logger.info("Environment setup completed successfully"); + return true; +} + +/** + * Export the constants for use in other files + */ +export const ENV_CONSTANTS = { + VENV_PATH, + RUN_PYTHON_IN_VENV, + RUN_PIP_IN_VENV, +}; diff --git a/yarn.lock b/yarn.lock index 235e1bc2..af88930e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4112,9 +4112,11 @@ __metadata: "@octokit/webhooks": ^14.0.2 "@open-swe/shared": "*" "@tsconfig/recommended": ^1.0.8 + "@types/commander": ^2.12.5 "@types/jest": ^29.5.0 "@types/jsonwebtoken": ^9.0.10 "@types/node": ^22.13.5 + commander: ^14.0.0 diff: ^8.0.1 dotenv: ^16.4.7 eslint: ^9.19.0 @@ -6704,6 +6706,15 @@ __metadata: languageName: node linkType: hard +"@types/commander@npm:^2.12.5": + version: 2.12.5 + resolution: "@types/commander@npm:2.12.5" + dependencies: + commander: "*" + checksum: 5b70bf09366f778f54cd0b831417aa1f749cdd9e95e81016d0537b801ad14275973b04c34ff4dfaa344986aa93b4518c2df12647ecf8b499b636585335ae3edc + languageName: node + linkType: hard + "@types/cookie@npm:^0.6.0": version: 0.6.0 resolution: "@types/cookie@npm:0.6.0" @@ -9050,6 +9061,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:*, commander@npm:^14.0.0": + version: 14.0.0 + resolution: "commander@npm:14.0.0" + checksum: 6e9bdaf2e8e4f512855ffc10579eeae2e84c4a7697a91b1a5f62aab3c9849182207855268dd7c3952ae7a2334312a7138f58e929e4b428aef5bf8af862685c9b + languageName: node + linkType: hard + "commander@npm:^10.0.0": version: 10.0.1 resolution: "commander@npm:10.0.1"