feat: GPT-5 support (#712)

* feat: GPT-5 support

* cr

* standardize sandbox usage, fix wrapping commands

* cr

* cr

* cr

* cr

* cr

* use uuid instead of math.random
This commit is contained in:
Brace Sproul 2025-08-07 16:31:43 -07:00 • committed by GitHub
parent 076219352a
commit ce3dbe0bff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 252 additions and 163 deletions

View file

@ -145,6 +145,7 @@ export async function runPytestOnFiles(
let parsed: Omit<TestResults, "success" | "error">;
try {
const jsonReportResult = await readFile({
config: {},
sandbox,
filePath: "/tmp/pytest_report.json",
workDir: repoDir,

View file

@ -173,7 +173,7 @@ export async function openPullRequest(
}
if (process.env.SKIP_CI_UNTIL_LAST_COMMIT === "true") {
await pushEmptyCommit(state.targetRepository, sandbox, {
await pushEmptyCommit(state.targetRepository, sandbox, config, {
githubInstallationToken,
});
}

View file

@ -263,7 +263,7 @@ export async function takeAction(
...toolCallResults,
]);
const codebaseTree = await getCodebaseTree(undefined, undefined, config);
const codebaseTree = await getCodebaseTree(config);
// If the codebase tree failed to generate, fallback to the previous codebase tree, or if that's not defined, use the failed to generate message.
const codebaseTreeToReturn =
codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE

View file

@ -189,11 +189,7 @@ export async function initializeSandbox(
};
emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
try {
const codebaseTree = await getCodebaseTree(
existingSandbox.id,
undefined,
config,
);
const codebaseTree = await getCodebaseTree(config, existingSandbox.id);
if (codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE) {
emitStepEvent(
baseGenerateCodebaseTreeAction,
@ -208,7 +204,11 @@ export async function initializeSandbox(
sandboxSessionId: existingSandbox.id,
codebaseTree,
messages: createEventsMessage(),
customRules: await getCustomRules(existingSandbox, absoluteRepoDir),
customRules: await getCustomRules(
existingSandbox,
absoluteRepoDir,
config,
),
};
} catch {
emitStepEvent(
@ -220,7 +220,11 @@ export async function initializeSandbox(
sandboxSessionId: existingSandbox.id,
codebaseTree: FAILED_TO_GENERATE_TREE_MESSAGE,
messages: createEventsMessage(),
customRules: await getCustomRules(existingSandbox, absoluteRepoDir),
customRules: await getCustomRules(
existingSandbox,
absoluteRepoDir,
config,
),
};
}
} catch {
@ -347,7 +351,7 @@ export async function initializeSandbox(
emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
let codebaseTree: string | undefined;
try {
codebaseTree = await getCodebaseTree(sandbox.id, undefined, config);
codebaseTree = await getCodebaseTree(config, sandbox.id);
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
} catch (_) {
emitStepEvent(
@ -363,7 +367,7 @@ export async function initializeSandbox(
codebaseTree,
messages: createEventsMessage(),
dependenciesInstalled: false,
customRules: await getCustomRules(sandbox, absoluteRepoDir),
customRules: await getCustomRules(sandbox, absoluteRepoDir, config),
branchName: newBranchName,
};
}
@ -455,7 +459,7 @@ async function initializeSandboxLocal(
let codebaseTree = undefined;
try {
codebaseTree = await getCodebaseTree(undefined, targetRepository, config);
codebaseTree = await getCodebaseTree(config, undefined, targetRepository);
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
} catch (_) {
emitStepEvent(

View file

@ -14,6 +14,7 @@ import {
} from "@open-swe/shared/open-swe/local-mode";
import { createShellExecutor } from "../utils/shell-executor/shell-executor.js";
import { join } from "path";
import { v4 as uuidv4 } from "uuid";
type FileOperationResult = {
success: boolean;
@ -38,11 +39,8 @@ async function applyPatchWithGit(
): Promise<FileOperationResult> {
// Generate temp patch file path
const tempPatchFile = isLocalMode(config)
? join(
workDir,
`patch_${Date.now()}_${Math.random().toString(36).substring(2)}.diff`,
)
: `/tmp/patch_${Date.now()}_${Math.random().toString(36).substring(2)}.diff`;
? join(workDir, `patch_${uuidv4()}.diff`)
: `/tmp/patch_${uuidv4()}.diff`;
try {
// Create the patch file using unified shell executor

View file

@ -2,53 +2,36 @@ import { Sandbox } from "@daytonaio/sdk";
import { readFile, writeFile } from "../../utils/read-write.js";
import { getSandboxErrorFields } from "../../utils/sandbox-error-fields.js";
import { GraphConfig } from "@open-swe/shared/open-swe/types";
import {
createShellExecutor,
LocalExecuteResponse,
} from "../../utils/shell-executor/index.js";
import { createShellExecutor } from "../../utils/shell-executor/index.js";
interface ViewCommandInputs {
path: string;
workDir: string;
viewRange?: [number, number];
}
export async function handleViewCommand(
sandbox: Sandbox,
path: string,
workDir: string,
viewRange?: [number, number],
config?: GraphConfig,
config: GraphConfig,
inputs: ViewCommandInputs,
): Promise<string> {
const { path, workDir, viewRange } = inputs;
try {
// Check if path is a directory
let statOutput: LocalExecuteResponse;
if (!config) {
// Fallback to direct sandbox execution if no config provided
statOutput = await sandbox.process.executeCommand(
`stat -c %F "${path}"`,
workDir,
);
} else {
const executor = createShellExecutor(config);
statOutput = await executor.executeCommand({
command: `stat -c %F "${path}"`,
workdir: workDir,
sandbox,
});
}
const executor = createShellExecutor(config);
const statOutput = await executor.executeCommand({
command: `stat -c %F "${path}"`,
workdir: workDir,
sandbox,
});
if (statOutput.exitCode === 0 && statOutput.result?.includes("directory")) {
// List directory contents
let lsOutput: LocalExecuteResponse;
if (!config) {
// Fallback to direct sandbox execution if no config provided
lsOutput = await sandbox.process.executeCommand(
`ls -la "${path}"`,
workDir,
);
} else {
const executor = createShellExecutor(config);
lsOutput = await executor.executeCommand({
command: `ls -la "${path}"`,
workdir: workDir,
sandbox,
});
}
const lsOutput = await executor.executeCommand({
command: `ls -la "${path}"`,
workdir: workDir,
sandbox,
});
if (lsOutput.exitCode !== 0) {
throw new Error(`Failed to list directory: ${lsOutput.result}`);
@ -99,17 +82,24 @@ export async function handleViewCommand(
}
}
interface StrReplaceCommandInputs {
path: string;
workDir: string;
oldStr: string;
newStr: string;
}
export async function handleStrReplaceCommand(
sandbox: Sandbox,
path: string,
workDir: string,
oldStr: string,
newStr: string,
config: GraphConfig,
inputs: StrReplaceCommandInputs,
): Promise<string> {
const { path, workDir, oldStr, newStr } = inputs;
const { success: readSuccess, output: fileContent } = await readFile({
sandbox,
filePath: path,
workDir,
config,
});
if (!readSuccess) {
@ -152,17 +142,24 @@ export async function handleStrReplaceCommand(
return `Successfully replaced text in ${path} at exactly one location.`;
}
interface CreateCommandInputs {
path: string;
workDir: string;
fileText: string;
}
export async function handleCreateCommand(
sandbox: Sandbox,
path: string,
workDir: string,
fileText: string,
config: GraphConfig,
inputs: CreateCommandInputs,
): Promise<string> {
const { path, workDir, fileText } = inputs;
// Check if file already exists
const { success: readSuccess } = await readFile({
sandbox,
filePath: path,
workDir,
config,
});
if (readSuccess) {
@ -185,17 +182,24 @@ export async function handleCreateCommand(
return `Successfully created file ${path}.`;
}
interface InsertCommandInputs {
path: string;
workDir: string;
insertLine: number;
newStr: string;
}
export async function handleInsertCommand(
sandbox: Sandbox,
path: string,
workDir: string,
insertLine: number,
newStr: string,
config: GraphConfig,
inputs: InsertCommandInputs,
): Promise<string> {
const { path, workDir, insertLine, newStr } = inputs;
const { success: readSuccess, output: fileContent } = await readFile({
sandbox,
filePath: path,
workDir,
config,
});
if (!readSuccess) {

View file

@ -169,12 +169,11 @@ export function createTextEditorTool(
switch (command) {
case "view":
result = await handleViewCommand(
sandbox,
result = await handleViewCommand(sandbox, config, {
path,
workDir,
view_range,
);
viewRange: view_range,
});
break;
case "str_replace":
if (!old_str || new_str === undefined) {
@ -182,24 +181,22 @@ export function createTextEditorTool(
"str_replace command requires both old_str and new_str parameters",
);
}
result = await handleStrReplaceCommand(
sandbox,
result = await handleStrReplaceCommand(sandbox, config, {
path,
workDir,
old_str,
new_str,
);
oldStr: old_str,
newStr: new_str,
});
break;
case "create":
if (!file_text) {
throw new Error("create command requires file_text parameter");
}
result = await handleCreateCommand(
sandbox,
result = await handleCreateCommand(sandbox, config, {
path,
workDir,
file_text,
);
fileText: file_text,
});
break;
case "insert":
if (insert_line === undefined || new_str === undefined) {
@ -207,13 +204,12 @@ export function createTextEditorTool(
"insert command requires both insert_line and new_str parameters",
);
}
result = await handleInsertCommand(
sandbox,
result = await handleInsertCommand(sandbox, config, {
path,
workDir,
insert_line,
new_str,
);
insertLine: insert_line,
newStr: new_str,
});
break;
default:
throw new Error(`Unknown command: ${command}`);

View file

@ -59,12 +59,11 @@ export function createViewTool(
} else {
// Sandbox mode: use existing handler
const sandbox = await getSandboxSessionOrThrow(input);
result = await handleViewCommand(
sandbox,
result = await handleViewCommand(sandbox, config, {
path,
workDir,
view_range as [number, number] | undefined,
);
viewRange: view_range as [number, number] | undefined,
});
}
logger.info(`View command executed successfully on ${path}`);

View file

@ -13,6 +13,7 @@ import {
formatGrepCommand,
} from "@open-swe/shared/open-swe/tools";
import { createShellExecutor } from "../utils/shell-executor/index.js";
import { wrapScript } from "../utils/wrap-script.js";
const logger = createLogger(LogLevel.INFO, "GrepTool");
@ -23,7 +24,7 @@ export function createGrepTool(
const grepTool = tool(
async (input): Promise<{ result: string; status: "success" | "error" }> => {
try {
const command = formatGrepCommand(input as any);
const command = formatGrepCommand(input);
const localMode = isLocalMode(config);
const localAbsolutePath = getLocalWorkingDirectory();
const sandboxAbsolutePath = getRepoAbsolutePath(state.targetRepository);
@ -36,7 +37,7 @@ export function createGrepTool(
const executor = createShellExecutor(config);
const response = await executor.executeCommand({
command,
command: wrapScript(command.join(" ")),
workdir: workDir,
timeout: TIMEOUT_SEC,
});

View file

@ -11,7 +11,7 @@ export async function getSandboxSessionOrThrow(
): Promise<Sandbox> {
let sandboxSessionId = "";
// Attempt to extract from input.
if ("xSandboxSessionId" in input) {
if ("xSandboxSessionId" in input && input.xSandboxSessionId) {
sandboxSessionId = input.xSandboxSessionId as string;
} else {
const state = getCurrentTaskInput<GraphState>();

View file

@ -9,6 +9,7 @@ import {
import { promises as fs } from "fs";
import { join } from "path";
import { GraphConfig } from "@open-swe/shared/open-swe/types";
import { createShellExecutor } from "./shell-executor/shell-executor.js";
const logger = createLogger(LogLevel.INFO, "CustomRules");
@ -124,18 +125,21 @@ export function parseCustomRulesFromString(
export async function getCustomRules(
sandbox: Sandbox,
rootDir: string,
config?: GraphConfig,
config: GraphConfig,
): Promise<CustomRules | undefined> {
try {
if (config && isLocalMode(config)) {
if (isLocalMode(config)) {
return getCustomRulesLocal(rootDir);
}
const executor = createShellExecutor(config);
const catAgentsMdFileCommand = ["cat", "AGENTS.md"];
const agentsMdRes = await sandbox.process.executeCommand(
catAgentsMdFileCommand.join(" "),
rootDir,
);
const agentsMdRes = await executor.executeCommand({
command: catAgentsMdFileCommand.join(" "),
workdir: rootDir,
sandbox,
});
if (agentsMdRes.exitCode === 0 && agentsMdRes.result?.length > 0) {
return parseCustomRulesFromString(agentsMdRes.result);
}
@ -144,9 +148,21 @@ export async function getCustomRules(
const catClaudeMdFileCommand = ["cat", "CLAUDE.md"];
const catCursorMdFileCommand = ["cat", "CURSOR.md"];
const [agentMdRes, claudeMdRes, cursorMdRes] = await Promise.all([
sandbox.process.executeCommand(catAgentMdFileCommand.join(" "), rootDir),
sandbox.process.executeCommand(catClaudeMdFileCommand.join(" "), rootDir),
sandbox.process.executeCommand(catCursorMdFileCommand.join(" "), rootDir),
executor.executeCommand({
command: catAgentMdFileCommand.join(" "),
workdir: rootDir,
sandbox,
}),
executor.executeCommand({
command: catClaudeMdFileCommand.join(" "),
workdir: rootDir,
sandbox,
}),
executor.executeCommand({
command: catCursorMdFileCommand.join(" "),
workdir: rootDir,
sandbox,
}),
]);
if (agentMdRes.exitCode === 0 && agentMdRes.result?.length > 0) {
return parseCustomRulesFromString(agentMdRes.result);

View file

@ -376,6 +376,7 @@ export async function checkoutBranchAndCommit(
export async function pushEmptyCommit(
targetRepository: TargetRepository,
sandbox: Sandbox,
config: GraphConfig,
options: {
githubInstallationToken: string;
},
@ -390,12 +391,12 @@ export async function pushEmptyCommit(
try {
const absoluteRepoDir = getRepoAbsolutePath(targetRepository);
const setGitConfigRes = await sandbox.process.executeCommand(
`git config user.name "${userName}" && git config user.email "${userEmail}"`,
absoluteRepoDir,
undefined,
TIMEOUT_SEC,
);
const executor = createShellExecutor(config);
const setGitConfigRes = await executor.executeCommand({
command: `git config user.name "${userName}" && git config user.email "${userEmail}"`,
workdir: absoluteRepoDir,
timeout: TIMEOUT_SEC,
});
if (setGitConfigRes.exitCode !== 0) {
logger.error(`Failed to set git config`, {
exitCode: setGitConfigRes.exitCode,
@ -404,12 +405,11 @@ export async function pushEmptyCommit(
return;
}
const emptyCommitRes = await sandbox.process.executeCommand(
"git commit --allow-empty -m 'Empty commit to trigger CI'",
absoluteRepoDir,
undefined,
TIMEOUT_SEC,
);
const emptyCommitRes = await executor.executeCommand({
command: "git commit --allow-empty -m 'Empty commit to trigger CI'",
workdir: absoluteRepoDir,
timeout: TIMEOUT_SEC,
});
if (emptyCommitRes.exitCode !== 0) {
logger.error(`Failed to push empty commit`, {
exitCode: emptyCommitRes.exitCode,

View file

@ -180,7 +180,6 @@ export class ModelManager {
const modelOptions: InitChatModelArgs = {
modelProvider: provider,
temperature: thinkingModel ? undefined : temperature,
max_retries: MAX_RETRIES,
...(apiKey ? { apiKey } : {}),
...(thinkingModel && provider === "anthropic"
@ -188,7 +187,15 @@ export class ModelManager {
thinking: { budget_tokens: thinkingBudgetTokens, type: "enabled" },
maxTokens: thinkingMaxTokens,
}
: { maxTokens: finalMaxTokens }),
: modelName.includes("gpt-5")
? {
max_completion_tokens: finalMaxTokens,
temperature: 1,
}
: {
maxTokens: finalMaxTokens,
temperature: thinkingModel ? undefined : temperature,
}),
};
logger.debug("Initializing model", {
@ -219,8 +226,17 @@ export class ModelManager {
selectedModelConfig = {
provider,
modelName,
temperature: defaultConfig.temperature ?? baseConfig.temperature,
maxTokens: defaultConfig.maxTokens ?? baseConfig.maxTokens,
...(modelName.includes("gpt-5")
? {
max_completion_tokens:
defaultConfig.maxTokens ?? baseConfig.maxTokens,
temperature: 1,
}
: {
maxTokens: defaultConfig.maxTokens ?? baseConfig.maxTokens,
temperature:
defaultConfig.temperature ?? baseConfig.temperature,
}),
...(isThinkingModel
? {
thinkingModel: true,
@ -247,8 +263,17 @@ export class ModelManager {
const fallbackConfig = {
...fallbackModel,
temperature: isThinkingModel ? undefined : baseConfig.temperature,
maxTokens: baseConfig.maxTokens,
...(fallbackModel.modelName.includes("gpt-5")
? {
max_completion_tokens: baseConfig.maxTokens,
temperature: 1,
}
: {
maxTokens: baseConfig.maxTokens,
temperature: isThinkingModel
? undefined
: baseConfig.temperature,
}),
...(isThinkingModel
? {
thinkingModel: true,
@ -331,8 +356,15 @@ export class ModelManager {
return {
modelName,
provider: modelProvider as Provider,
temperature: taskConfig.temperature,
maxTokens: config.configurable?.maxTokens ?? 10_000,
...(modelName.includes("gpt-5")
? {
max_completion_tokens: config.configurable?.maxTokens ?? 10_000,
temperature: 1,
}
: {
maxTokens: config.configurable?.maxTokens ?? 10_000,
temperature: taskConfig.temperature,
}),
thinkingModel,
thinkingBudgetTokens,
};
@ -361,11 +393,11 @@ export class ModelManager {
[LLMTask.SUMMARIZER]: "gemini-2.5-pro",
},
openai: {
[LLMTask.PLANNER]: "o3",
[LLMTask.PROGRAMMER]: "gpt-4.1",
[LLMTask.REVIEWER]: "o3",
[LLMTask.ROUTER]: "gpt-4o-mini",
[LLMTask.SUMMARIZER]: "gpt-4.1-mini",
[LLMTask.PLANNER]: "gpt-5",
[LLMTask.PROGRAMMER]: "gpt-5",
[LLMTask.REVIEWER]: "gpt-5",
[LLMTask.ROUTER]: "gpt-5-nano",
[LLMTask.SUMMARIZER]: "gpt-5-mini",
},
};

View file

@ -9,27 +9,30 @@ import {
import { promises as fs } from "fs";
import { join, isAbsolute } from "path";
import { GraphConfig } from "@open-swe/shared/open-swe/types";
import { createShellExecutor } from "./shell-executor/shell-executor.js";
import { v4 as uuidv4 } from "uuid";
const logger = createLogger(LogLevel.INFO, "ReadWriteUtil");
async function handleCreateFile(
sandbox: Sandbox,
sandbox: Sandbox | null,
filePath: string,
config: GraphConfig,
args?: {
workDir?: string;
},
config?: GraphConfig,
) {
if (config && isLocalMode(config)) {
if (isLocalMode(config)) {
return handleCreateFileLocal(filePath, args?.workDir);
}
try {
const touchCommand = `touch "${filePath}"`;
const touchOutput = await sandbox.process.executeCommand(
touchCommand,
args?.workDir,
);
const executor = createShellExecutor(config);
const touchOutput = await executor.executeCommand({
command: `touch "${filePath}"`,
workdir: args?.workDir,
sandbox: sandbox ?? undefined,
});
return touchOutput;
} catch (e) {
const errorFields = getSandboxErrorFields(e);
@ -49,26 +52,25 @@ async function readFileFunc(inputs: {
sandbox: Sandbox | null;
filePath: string;
workDir?: string;
config?: GraphConfig;
config: GraphConfig;
}): Promise<{
success: boolean;
output: string;
}> {
const { sandbox, filePath, workDir, config } = inputs;
if (config && isLocalMode(config)) {
if (isLocalMode(config)) {
return readFileLocal(filePath, workDir);
}
if (!sandbox) {
throw new Error("Sandbox is required when not in local mode");
}
const executor = createShellExecutor(config);
try {
const readOutput = await sandbox.process.executeCommand(
`cat "${filePath}"`,
workDir,
);
const readOutput = await executor.executeCommand({
command: `cat "${filePath}"`,
workdir: workDir,
sandbox: sandbox ?? undefined,
});
if (readOutput.exitCode !== 0) {
const errorResult = readOutput.result ?? readOutput.artifacts?.stdout;
@ -90,7 +92,7 @@ async function readFileFunc(inputs: {
createOutput = await handleCreateFileLocal(filePath, workDir);
} else {
// Sandbox mode: use handleCreateFile
createOutput = await handleCreateFile(sandbox, filePath, {
createOutput = await handleCreateFile(sandbox, filePath, config, {
workDir,
});
}
@ -164,14 +166,16 @@ async function writeFileFunc(inputs: {
}
try {
const delimiter = "EOF_" + Date.now() + "_" + Math.random().toString(36);
const delimiter = `EOF_${uuidv4()}`;
const writeCommand = `cat > "${filePath}" << '${delimiter}'
${content}
${delimiter}`;
const writeOutput = await sandbox.process.executeCommand(
writeCommand,
workDir,
);
const executor = createShellExecutor(config);
const writeOutput = await executor.executeCommand({
command: writeCommand,
workdir: workDir,
sandbox: sandbox ?? undefined,
});
if (writeOutput.exitCode !== 0) {
const errorResult = writeOutput.result ?? writeOutput.artifacts?.stdout;

View file

@ -169,9 +169,9 @@ export async function getSandboxWithErrorHandling(
// Get codebase tree
const codebaseTree = await getCodebaseTree(
config,
sandbox.id,
targetRepository,
config,
);
const codebaseTreeToReturn =
codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE ? null : codebaseTree;

View file

@ -40,6 +40,7 @@ export class ShellExecutor {
env = {},
timeout = TIMEOUT_SEC,
sandbox,
sandboxSessionId,
} = options;
const commandString = Array.isArray(command) ? command.join(" ") : command;
@ -60,6 +61,7 @@ export class ShellExecutor {
environment,
timeout,
sandbox,
sandboxSessionId,
);
}
}
@ -93,8 +95,13 @@ export class ShellExecutor {
env?: Record<string, string>,
timeout?: number,
sandbox?: Sandbox,
sandboxSessionId?: string,
): Promise<LocalExecuteResponse> {
const sandbox_ = sandbox ?? (await getSandboxSessionOrThrow({}));
const sandbox_ =
sandbox ??
(await getSandboxSessionOrThrow({
xSandboxSessionId: sandboxSessionId,
}));
return await sandbox_.process.executeCommand(
command,

View file

@ -15,4 +15,5 @@ export interface ExecuteCommandOptions {
env?: Record<string, string>;
timeout?: number;
sandbox?: Sandbox;
sandboxSessionId?: string;
}

View file

@ -4,7 +4,6 @@ import {
TargetRepository,
GraphConfig,
} from "@open-swe/shared/open-swe/types";
import { daytonaClient } from "./sandbox.js";
import { createLogger, LogLevel } from "./logger.js";
import path from "node:path";
import { SANDBOX_ROOT_DIR, TIMEOUT_SEC } from "@open-swe/shared/constants";
@ -18,9 +17,9 @@ export const FAILED_TO_GENERATE_TREE_MESSAGE =
"Failed to generate tree. Please try again.";
export async function getCodebaseTree(
config: GraphConfig,
sandboxSessionId_?: string,
targetRepository_?: TargetRepository,
config?: GraphConfig,
): Promise<string> {
try {
const command = `git ls-files | tree --fromfile -L 3`;
@ -28,7 +27,7 @@ export async function getCodebaseTree(
let targetRepository = targetRepository_;
// Check if we're in local mode
if (config && isLocalMode(config)) {
if (isLocalMode(config)) {
return getCodebaseTreeLocal(config);
}
@ -53,14 +52,14 @@ export async function getCodebaseTree(
throw new Error("Failed generate tree: No target repository provided");
}
const sandbox = await daytonaClient().get(sandboxSessionId);
const executor = createShellExecutor(config);
const repoDir = path.join(SANDBOX_ROOT_DIR, targetRepository.repo);
const response = await sandbox.process.executeCommand(
const response = await executor.executeCommand({
command,
repoDir,
undefined,
TIMEOUT_SEC,
);
workdir: repoDir,
timeout: TIMEOUT_SEC,
sandboxSessionId,
});
if (response.exitCode !== 0) {
logger.error("Failed to generate tree", {

View file

@ -1,7 +1,22 @@
import { v4 as uuidv4 } from "uuid";
export function wrapScript(command: string): string {
// Use bash directly to avoid script command compatibility issues
return `bash -c "$(cat <<'OPEN_SWE_X'
const makeDelim = () => `OPEN_SWE_${uuidv4()}`;
// Ensure the delimiter does not appear as a standalone line in the command
let delim = makeDelim();
const containsStandalone = (d: string) =>
command === d ||
command.startsWith(`${d}\n`) ||
command.endsWith(`\n${d}`) ||
command.includes(`\n${d}\n`);
while (containsStandalone(delim)) {
delim = makeDelim();
}
return `script --return --quiet -c "$(cat <<'${delim}'
${command}
OPEN_SWE_X
)"`;
${delim}
)" /dev/null`;
}

View file

@ -32,6 +32,18 @@ export const MODEL_OPTIONS = [
label: "Claude 3.5 Haiku",
value: "anthropic:claude-3-5-haiku-latest",
},
{
label: "GPT 5",
value: "openai:gpt-5",
},
{
label: "GPT 5 mini",
value: "openai:gpt-5-mini",
},
{
label: "GPT 5 nano",
value: "openai:gpt-5-nano",
},
{
label: "o4",
value: "openai:o4",