mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-04 19:32:12 +00:00
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:
parent
076219352a
commit
ce3dbe0bff
20 changed files with 252 additions and 163 deletions
|
|
@ -145,6 +145,7 @@ export async function runPytestOnFiles(
|
||||||
let parsed: Omit<TestResults, "success" | "error">;
|
let parsed: Omit<TestResults, "success" | "error">;
|
||||||
try {
|
try {
|
||||||
const jsonReportResult = await readFile({
|
const jsonReportResult = await readFile({
|
||||||
|
config: {},
|
||||||
sandbox,
|
sandbox,
|
||||||
filePath: "/tmp/pytest_report.json",
|
filePath: "/tmp/pytest_report.json",
|
||||||
workDir: repoDir,
|
workDir: repoDir,
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ export async function openPullRequest(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.SKIP_CI_UNTIL_LAST_COMMIT === "true") {
|
if (process.env.SKIP_CI_UNTIL_LAST_COMMIT === "true") {
|
||||||
await pushEmptyCommit(state.targetRepository, sandbox, {
|
await pushEmptyCommit(state.targetRepository, sandbox, config, {
|
||||||
githubInstallationToken,
|
githubInstallationToken,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ export async function takeAction(
|
||||||
...toolCallResults,
|
...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.
|
// 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 =
|
const codebaseTreeToReturn =
|
||||||
codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE
|
codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE
|
||||||
|
|
|
||||||
|
|
@ -189,11 +189,7 @@ export async function initializeSandbox(
|
||||||
};
|
};
|
||||||
emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
|
emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
|
||||||
try {
|
try {
|
||||||
const codebaseTree = await getCodebaseTree(
|
const codebaseTree = await getCodebaseTree(config, existingSandbox.id);
|
||||||
existingSandbox.id,
|
|
||||||
undefined,
|
|
||||||
config,
|
|
||||||
);
|
|
||||||
if (codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE) {
|
if (codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE) {
|
||||||
emitStepEvent(
|
emitStepEvent(
|
||||||
baseGenerateCodebaseTreeAction,
|
baseGenerateCodebaseTreeAction,
|
||||||
|
|
@ -208,7 +204,11 @@ export async function initializeSandbox(
|
||||||
sandboxSessionId: existingSandbox.id,
|
sandboxSessionId: existingSandbox.id,
|
||||||
codebaseTree,
|
codebaseTree,
|
||||||
messages: createEventsMessage(),
|
messages: createEventsMessage(),
|
||||||
customRules: await getCustomRules(existingSandbox, absoluteRepoDir),
|
customRules: await getCustomRules(
|
||||||
|
existingSandbox,
|
||||||
|
absoluteRepoDir,
|
||||||
|
config,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
emitStepEvent(
|
emitStepEvent(
|
||||||
|
|
@ -220,7 +220,11 @@ export async function initializeSandbox(
|
||||||
sandboxSessionId: existingSandbox.id,
|
sandboxSessionId: existingSandbox.id,
|
||||||
codebaseTree: FAILED_TO_GENERATE_TREE_MESSAGE,
|
codebaseTree: FAILED_TO_GENERATE_TREE_MESSAGE,
|
||||||
messages: createEventsMessage(),
|
messages: createEventsMessage(),
|
||||||
customRules: await getCustomRules(existingSandbox, absoluteRepoDir),
|
customRules: await getCustomRules(
|
||||||
|
existingSandbox,
|
||||||
|
absoluteRepoDir,
|
||||||
|
config,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -347,7 +351,7 @@ export async function initializeSandbox(
|
||||||
emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
|
emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
|
||||||
let codebaseTree: string | undefined;
|
let codebaseTree: string | undefined;
|
||||||
try {
|
try {
|
||||||
codebaseTree = await getCodebaseTree(sandbox.id, undefined, config);
|
codebaseTree = await getCodebaseTree(config, sandbox.id);
|
||||||
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
|
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
emitStepEvent(
|
emitStepEvent(
|
||||||
|
|
@ -363,7 +367,7 @@ export async function initializeSandbox(
|
||||||
codebaseTree,
|
codebaseTree,
|
||||||
messages: createEventsMessage(),
|
messages: createEventsMessage(),
|
||||||
dependenciesInstalled: false,
|
dependenciesInstalled: false,
|
||||||
customRules: await getCustomRules(sandbox, absoluteRepoDir),
|
customRules: await getCustomRules(sandbox, absoluteRepoDir, config),
|
||||||
branchName: newBranchName,
|
branchName: newBranchName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -455,7 +459,7 @@ async function initializeSandboxLocal(
|
||||||
|
|
||||||
let codebaseTree = undefined;
|
let codebaseTree = undefined;
|
||||||
try {
|
try {
|
||||||
codebaseTree = await getCodebaseTree(undefined, targetRepository, config);
|
codebaseTree = await getCodebaseTree(config, undefined, targetRepository);
|
||||||
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
|
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
emitStepEvent(
|
emitStepEvent(
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
} from "@open-swe/shared/open-swe/local-mode";
|
} from "@open-swe/shared/open-swe/local-mode";
|
||||||
import { createShellExecutor } from "../utils/shell-executor/shell-executor.js";
|
import { createShellExecutor } from "../utils/shell-executor/shell-executor.js";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
type FileOperationResult = {
|
type FileOperationResult = {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|
@ -38,11 +39,8 @@ async function applyPatchWithGit(
|
||||||
): Promise<FileOperationResult> {
|
): Promise<FileOperationResult> {
|
||||||
// Generate temp patch file path
|
// Generate temp patch file path
|
||||||
const tempPatchFile = isLocalMode(config)
|
const tempPatchFile = isLocalMode(config)
|
||||||
? join(
|
? join(workDir, `patch_${uuidv4()}.diff`)
|
||||||
workDir,
|
: `/tmp/patch_${uuidv4()}.diff`;
|
||||||
`patch_${Date.now()}_${Math.random().toString(36).substring(2)}.diff`,
|
|
||||||
)
|
|
||||||
: `/tmp/patch_${Date.now()}_${Math.random().toString(36).substring(2)}.diff`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create the patch file using unified shell executor
|
// Create the patch file using unified shell executor
|
||||||
|
|
|
||||||
|
|
@ -2,53 +2,36 @@ import { Sandbox } from "@daytonaio/sdk";
|
||||||
import { readFile, writeFile } from "../../utils/read-write.js";
|
import { readFile, writeFile } from "../../utils/read-write.js";
|
||||||
import { getSandboxErrorFields } from "../../utils/sandbox-error-fields.js";
|
import { getSandboxErrorFields } from "../../utils/sandbox-error-fields.js";
|
||||||
import { GraphConfig } from "@open-swe/shared/open-swe/types";
|
import { GraphConfig } from "@open-swe/shared/open-swe/types";
|
||||||
import {
|
import { createShellExecutor } from "../../utils/shell-executor/index.js";
|
||||||
createShellExecutor,
|
|
||||||
LocalExecuteResponse,
|
interface ViewCommandInputs {
|
||||||
} from "../../utils/shell-executor/index.js";
|
path: string;
|
||||||
|
workDir: string;
|
||||||
|
viewRange?: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
export async function handleViewCommand(
|
export async function handleViewCommand(
|
||||||
sandbox: Sandbox,
|
sandbox: Sandbox,
|
||||||
path: string,
|
config: GraphConfig,
|
||||||
workDir: string,
|
inputs: ViewCommandInputs,
|
||||||
viewRange?: [number, number],
|
|
||||||
config?: GraphConfig,
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
const { path, workDir, viewRange } = inputs;
|
||||||
try {
|
try {
|
||||||
// Check if path is a directory
|
// Check if path is a directory
|
||||||
let statOutput: LocalExecuteResponse;
|
const executor = createShellExecutor(config);
|
||||||
if (!config) {
|
const statOutput = await executor.executeCommand({
|
||||||
// Fallback to direct sandbox execution if no config provided
|
command: `stat -c %F "${path}"`,
|
||||||
statOutput = await sandbox.process.executeCommand(
|
workdir: workDir,
|
||||||
`stat -c %F "${path}"`,
|
sandbox,
|
||||||
workDir,
|
});
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const executor = createShellExecutor(config);
|
|
||||||
statOutput = await executor.executeCommand({
|
|
||||||
command: `stat -c %F "${path}"`,
|
|
||||||
workdir: workDir,
|
|
||||||
sandbox,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statOutput.exitCode === 0 && statOutput.result?.includes("directory")) {
|
if (statOutput.exitCode === 0 && statOutput.result?.includes("directory")) {
|
||||||
// List directory contents
|
// List directory contents
|
||||||
let lsOutput: LocalExecuteResponse;
|
const lsOutput = await executor.executeCommand({
|
||||||
if (!config) {
|
command: `ls -la "${path}"`,
|
||||||
// Fallback to direct sandbox execution if no config provided
|
workdir: workDir,
|
||||||
lsOutput = await sandbox.process.executeCommand(
|
sandbox,
|
||||||
`ls -la "${path}"`,
|
});
|
||||||
workDir,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const executor = createShellExecutor(config);
|
|
||||||
lsOutput = await executor.executeCommand({
|
|
||||||
command: `ls -la "${path}"`,
|
|
||||||
workdir: workDir,
|
|
||||||
sandbox,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lsOutput.exitCode !== 0) {
|
if (lsOutput.exitCode !== 0) {
|
||||||
throw new Error(`Failed to list directory: ${lsOutput.result}`);
|
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(
|
export async function handleStrReplaceCommand(
|
||||||
sandbox: Sandbox,
|
sandbox: Sandbox,
|
||||||
path: string,
|
config: GraphConfig,
|
||||||
workDir: string,
|
inputs: StrReplaceCommandInputs,
|
||||||
oldStr: string,
|
|
||||||
newStr: string,
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
const { path, workDir, oldStr, newStr } = inputs;
|
||||||
const { success: readSuccess, output: fileContent } = await readFile({
|
const { success: readSuccess, output: fileContent } = await readFile({
|
||||||
sandbox,
|
sandbox,
|
||||||
filePath: path,
|
filePath: path,
|
||||||
workDir,
|
workDir,
|
||||||
|
config,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!readSuccess) {
|
if (!readSuccess) {
|
||||||
|
|
@ -152,17 +142,24 @@ export async function handleStrReplaceCommand(
|
||||||
return `Successfully replaced text in ${path} at exactly one location.`;
|
return `Successfully replaced text in ${path} at exactly one location.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateCommandInputs {
|
||||||
|
path: string;
|
||||||
|
workDir: string;
|
||||||
|
fileText: string;
|
||||||
|
}
|
||||||
|
|
||||||
export async function handleCreateCommand(
|
export async function handleCreateCommand(
|
||||||
sandbox: Sandbox,
|
sandbox: Sandbox,
|
||||||
path: string,
|
config: GraphConfig,
|
||||||
workDir: string,
|
inputs: CreateCommandInputs,
|
||||||
fileText: string,
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
const { path, workDir, fileText } = inputs;
|
||||||
// Check if file already exists
|
// Check if file already exists
|
||||||
const { success: readSuccess } = await readFile({
|
const { success: readSuccess } = await readFile({
|
||||||
sandbox,
|
sandbox,
|
||||||
filePath: path,
|
filePath: path,
|
||||||
workDir,
|
workDir,
|
||||||
|
config,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (readSuccess) {
|
if (readSuccess) {
|
||||||
|
|
@ -185,17 +182,24 @@ export async function handleCreateCommand(
|
||||||
return `Successfully created file ${path}.`;
|
return `Successfully created file ${path}.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface InsertCommandInputs {
|
||||||
|
path: string;
|
||||||
|
workDir: string;
|
||||||
|
insertLine: number;
|
||||||
|
newStr: string;
|
||||||
|
}
|
||||||
|
|
||||||
export async function handleInsertCommand(
|
export async function handleInsertCommand(
|
||||||
sandbox: Sandbox,
|
sandbox: Sandbox,
|
||||||
path: string,
|
config: GraphConfig,
|
||||||
workDir: string,
|
inputs: InsertCommandInputs,
|
||||||
insertLine: number,
|
|
||||||
newStr: string,
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
const { path, workDir, insertLine, newStr } = inputs;
|
||||||
const { success: readSuccess, output: fileContent } = await readFile({
|
const { success: readSuccess, output: fileContent } = await readFile({
|
||||||
sandbox,
|
sandbox,
|
||||||
filePath: path,
|
filePath: path,
|
||||||
workDir,
|
workDir,
|
||||||
|
config,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!readSuccess) {
|
if (!readSuccess) {
|
||||||
|
|
|
||||||
|
|
@ -169,12 +169,11 @@ export function createTextEditorTool(
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case "view":
|
case "view":
|
||||||
result = await handleViewCommand(
|
result = await handleViewCommand(sandbox, config, {
|
||||||
sandbox,
|
|
||||||
path,
|
path,
|
||||||
workDir,
|
workDir,
|
||||||
view_range,
|
viewRange: view_range,
|
||||||
);
|
});
|
||||||
break;
|
break;
|
||||||
case "str_replace":
|
case "str_replace":
|
||||||
if (!old_str || new_str === undefined) {
|
if (!old_str || new_str === undefined) {
|
||||||
|
|
@ -182,24 +181,22 @@ export function createTextEditorTool(
|
||||||
"str_replace command requires both old_str and new_str parameters",
|
"str_replace command requires both old_str and new_str parameters",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
result = await handleStrReplaceCommand(
|
result = await handleStrReplaceCommand(sandbox, config, {
|
||||||
sandbox,
|
|
||||||
path,
|
path,
|
||||||
workDir,
|
workDir,
|
||||||
old_str,
|
oldStr: old_str,
|
||||||
new_str,
|
newStr: new_str,
|
||||||
);
|
});
|
||||||
break;
|
break;
|
||||||
case "create":
|
case "create":
|
||||||
if (!file_text) {
|
if (!file_text) {
|
||||||
throw new Error("create command requires file_text parameter");
|
throw new Error("create command requires file_text parameter");
|
||||||
}
|
}
|
||||||
result = await handleCreateCommand(
|
result = await handleCreateCommand(sandbox, config, {
|
||||||
sandbox,
|
|
||||||
path,
|
path,
|
||||||
workDir,
|
workDir,
|
||||||
file_text,
|
fileText: file_text,
|
||||||
);
|
});
|
||||||
break;
|
break;
|
||||||
case "insert":
|
case "insert":
|
||||||
if (insert_line === undefined || new_str === undefined) {
|
if (insert_line === undefined || new_str === undefined) {
|
||||||
|
|
@ -207,13 +204,12 @@ export function createTextEditorTool(
|
||||||
"insert command requires both insert_line and new_str parameters",
|
"insert command requires both insert_line and new_str parameters",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
result = await handleInsertCommand(
|
result = await handleInsertCommand(sandbox, config, {
|
||||||
sandbox,
|
|
||||||
path,
|
path,
|
||||||
workDir,
|
workDir,
|
||||||
insert_line,
|
insertLine: insert_line,
|
||||||
new_str,
|
newStr: new_str,
|
||||||
);
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown command: ${command}`);
|
throw new Error(`Unknown command: ${command}`);
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,11 @@ export function createViewTool(
|
||||||
} else {
|
} else {
|
||||||
// Sandbox mode: use existing handler
|
// Sandbox mode: use existing handler
|
||||||
const sandbox = await getSandboxSessionOrThrow(input);
|
const sandbox = await getSandboxSessionOrThrow(input);
|
||||||
result = await handleViewCommand(
|
result = await handleViewCommand(sandbox, config, {
|
||||||
sandbox,
|
|
||||||
path,
|
path,
|
||||||
workDir,
|
workDir,
|
||||||
view_range as [number, number] | undefined,
|
viewRange: view_range as [number, number] | undefined,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`View command executed successfully on ${path}`);
|
logger.info(`View command executed successfully on ${path}`);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
formatGrepCommand,
|
formatGrepCommand,
|
||||||
} from "@open-swe/shared/open-swe/tools";
|
} from "@open-swe/shared/open-swe/tools";
|
||||||
import { createShellExecutor } from "../utils/shell-executor/index.js";
|
import { createShellExecutor } from "../utils/shell-executor/index.js";
|
||||||
|
import { wrapScript } from "../utils/wrap-script.js";
|
||||||
|
|
||||||
const logger = createLogger(LogLevel.INFO, "GrepTool");
|
const logger = createLogger(LogLevel.INFO, "GrepTool");
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@ export function createGrepTool(
|
||||||
const grepTool = tool(
|
const grepTool = tool(
|
||||||
async (input): Promise<{ result: string; status: "success" | "error" }> => {
|
async (input): Promise<{ result: string; status: "success" | "error" }> => {
|
||||||
try {
|
try {
|
||||||
const command = formatGrepCommand(input as any);
|
const command = formatGrepCommand(input);
|
||||||
const localMode = isLocalMode(config);
|
const localMode = isLocalMode(config);
|
||||||
const localAbsolutePath = getLocalWorkingDirectory();
|
const localAbsolutePath = getLocalWorkingDirectory();
|
||||||
const sandboxAbsolutePath = getRepoAbsolutePath(state.targetRepository);
|
const sandboxAbsolutePath = getRepoAbsolutePath(state.targetRepository);
|
||||||
|
|
@ -36,7 +37,7 @@ export function createGrepTool(
|
||||||
|
|
||||||
const executor = createShellExecutor(config);
|
const executor = createShellExecutor(config);
|
||||||
const response = await executor.executeCommand({
|
const response = await executor.executeCommand({
|
||||||
command,
|
command: wrapScript(command.join(" ")),
|
||||||
workdir: workDir,
|
workdir: workDir,
|
||||||
timeout: TIMEOUT_SEC,
|
timeout: TIMEOUT_SEC,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export async function getSandboxSessionOrThrow(
|
||||||
): Promise<Sandbox> {
|
): Promise<Sandbox> {
|
||||||
let sandboxSessionId = "";
|
let sandboxSessionId = "";
|
||||||
// Attempt to extract from input.
|
// Attempt to extract from input.
|
||||||
if ("xSandboxSessionId" in input) {
|
if ("xSandboxSessionId" in input && input.xSandboxSessionId) {
|
||||||
sandboxSessionId = input.xSandboxSessionId as string;
|
sandboxSessionId = input.xSandboxSessionId as string;
|
||||||
} else {
|
} else {
|
||||||
const state = getCurrentTaskInput<GraphState>();
|
const state = getCurrentTaskInput<GraphState>();
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
import { promises as fs } from "fs";
|
import { promises as fs } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { GraphConfig } from "@open-swe/shared/open-swe/types";
|
import { GraphConfig } from "@open-swe/shared/open-swe/types";
|
||||||
|
import { createShellExecutor } from "./shell-executor/shell-executor.js";
|
||||||
|
|
||||||
const logger = createLogger(LogLevel.INFO, "CustomRules");
|
const logger = createLogger(LogLevel.INFO, "CustomRules");
|
||||||
|
|
||||||
|
|
@ -124,18 +125,21 @@ export function parseCustomRulesFromString(
|
||||||
export async function getCustomRules(
|
export async function getCustomRules(
|
||||||
sandbox: Sandbox,
|
sandbox: Sandbox,
|
||||||
rootDir: string,
|
rootDir: string,
|
||||||
config?: GraphConfig,
|
config: GraphConfig,
|
||||||
): Promise<CustomRules | undefined> {
|
): Promise<CustomRules | undefined> {
|
||||||
try {
|
try {
|
||||||
if (config && isLocalMode(config)) {
|
if (isLocalMode(config)) {
|
||||||
return getCustomRulesLocal(rootDir);
|
return getCustomRulesLocal(rootDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const executor = createShellExecutor(config);
|
||||||
|
|
||||||
const catAgentsMdFileCommand = ["cat", "AGENTS.md"];
|
const catAgentsMdFileCommand = ["cat", "AGENTS.md"];
|
||||||
const agentsMdRes = await sandbox.process.executeCommand(
|
const agentsMdRes = await executor.executeCommand({
|
||||||
catAgentsMdFileCommand.join(" "),
|
command: catAgentsMdFileCommand.join(" "),
|
||||||
rootDir,
|
workdir: rootDir,
|
||||||
);
|
sandbox,
|
||||||
|
});
|
||||||
if (agentsMdRes.exitCode === 0 && agentsMdRes.result?.length > 0) {
|
if (agentsMdRes.exitCode === 0 && agentsMdRes.result?.length > 0) {
|
||||||
return parseCustomRulesFromString(agentsMdRes.result);
|
return parseCustomRulesFromString(agentsMdRes.result);
|
||||||
}
|
}
|
||||||
|
|
@ -144,9 +148,21 @@ export async function getCustomRules(
|
||||||
const catClaudeMdFileCommand = ["cat", "CLAUDE.md"];
|
const catClaudeMdFileCommand = ["cat", "CLAUDE.md"];
|
||||||
const catCursorMdFileCommand = ["cat", "CURSOR.md"];
|
const catCursorMdFileCommand = ["cat", "CURSOR.md"];
|
||||||
const [agentMdRes, claudeMdRes, cursorMdRes] = await Promise.all([
|
const [agentMdRes, claudeMdRes, cursorMdRes] = await Promise.all([
|
||||||
sandbox.process.executeCommand(catAgentMdFileCommand.join(" "), rootDir),
|
executor.executeCommand({
|
||||||
sandbox.process.executeCommand(catClaudeMdFileCommand.join(" "), rootDir),
|
command: catAgentMdFileCommand.join(" "),
|
||||||
sandbox.process.executeCommand(catCursorMdFileCommand.join(" "), rootDir),
|
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) {
|
if (agentMdRes.exitCode === 0 && agentMdRes.result?.length > 0) {
|
||||||
return parseCustomRulesFromString(agentMdRes.result);
|
return parseCustomRulesFromString(agentMdRes.result);
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,7 @@ export async function checkoutBranchAndCommit(
|
||||||
export async function pushEmptyCommit(
|
export async function pushEmptyCommit(
|
||||||
targetRepository: TargetRepository,
|
targetRepository: TargetRepository,
|
||||||
sandbox: Sandbox,
|
sandbox: Sandbox,
|
||||||
|
config: GraphConfig,
|
||||||
options: {
|
options: {
|
||||||
githubInstallationToken: string;
|
githubInstallationToken: string;
|
||||||
},
|
},
|
||||||
|
|
@ -390,12 +391,12 @@ export async function pushEmptyCommit(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const absoluteRepoDir = getRepoAbsolutePath(targetRepository);
|
const absoluteRepoDir = getRepoAbsolutePath(targetRepository);
|
||||||
const setGitConfigRes = await sandbox.process.executeCommand(
|
const executor = createShellExecutor(config);
|
||||||
`git config user.name "${userName}" && git config user.email "${userEmail}"`,
|
const setGitConfigRes = await executor.executeCommand({
|
||||||
absoluteRepoDir,
|
command: `git config user.name "${userName}" && git config user.email "${userEmail}"`,
|
||||||
undefined,
|
workdir: absoluteRepoDir,
|
||||||
TIMEOUT_SEC,
|
timeout: TIMEOUT_SEC,
|
||||||
);
|
});
|
||||||
if (setGitConfigRes.exitCode !== 0) {
|
if (setGitConfigRes.exitCode !== 0) {
|
||||||
logger.error(`Failed to set git config`, {
|
logger.error(`Failed to set git config`, {
|
||||||
exitCode: setGitConfigRes.exitCode,
|
exitCode: setGitConfigRes.exitCode,
|
||||||
|
|
@ -404,12 +405,11 @@ export async function pushEmptyCommit(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyCommitRes = await sandbox.process.executeCommand(
|
const emptyCommitRes = await executor.executeCommand({
|
||||||
"git commit --allow-empty -m 'Empty commit to trigger CI'",
|
command: "git commit --allow-empty -m 'Empty commit to trigger CI'",
|
||||||
absoluteRepoDir,
|
workdir: absoluteRepoDir,
|
||||||
undefined,
|
timeout: TIMEOUT_SEC,
|
||||||
TIMEOUT_SEC,
|
});
|
||||||
);
|
|
||||||
if (emptyCommitRes.exitCode !== 0) {
|
if (emptyCommitRes.exitCode !== 0) {
|
||||||
logger.error(`Failed to push empty commit`, {
|
logger.error(`Failed to push empty commit`, {
|
||||||
exitCode: emptyCommitRes.exitCode,
|
exitCode: emptyCommitRes.exitCode,
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,6 @@ export class ModelManager {
|
||||||
|
|
||||||
const modelOptions: InitChatModelArgs = {
|
const modelOptions: InitChatModelArgs = {
|
||||||
modelProvider: provider,
|
modelProvider: provider,
|
||||||
temperature: thinkingModel ? undefined : temperature,
|
|
||||||
max_retries: MAX_RETRIES,
|
max_retries: MAX_RETRIES,
|
||||||
...(apiKey ? { apiKey } : {}),
|
...(apiKey ? { apiKey } : {}),
|
||||||
...(thinkingModel && provider === "anthropic"
|
...(thinkingModel && provider === "anthropic"
|
||||||
|
|
@ -188,7 +187,15 @@ export class ModelManager {
|
||||||
thinking: { budget_tokens: thinkingBudgetTokens, type: "enabled" },
|
thinking: { budget_tokens: thinkingBudgetTokens, type: "enabled" },
|
||||||
maxTokens: thinkingMaxTokens,
|
maxTokens: thinkingMaxTokens,
|
||||||
}
|
}
|
||||||
: { maxTokens: finalMaxTokens }),
|
: modelName.includes("gpt-5")
|
||||||
|
? {
|
||||||
|
max_completion_tokens: finalMaxTokens,
|
||||||
|
temperature: 1,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
maxTokens: finalMaxTokens,
|
||||||
|
temperature: thinkingModel ? undefined : temperature,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.debug("Initializing model", {
|
logger.debug("Initializing model", {
|
||||||
|
|
@ -219,8 +226,17 @@ export class ModelManager {
|
||||||
selectedModelConfig = {
|
selectedModelConfig = {
|
||||||
provider,
|
provider,
|
||||||
modelName,
|
modelName,
|
||||||
temperature: defaultConfig.temperature ?? baseConfig.temperature,
|
...(modelName.includes("gpt-5")
|
||||||
maxTokens: defaultConfig.maxTokens ?? baseConfig.maxTokens,
|
? {
|
||||||
|
max_completion_tokens:
|
||||||
|
defaultConfig.maxTokens ?? baseConfig.maxTokens,
|
||||||
|
temperature: 1,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
maxTokens: defaultConfig.maxTokens ?? baseConfig.maxTokens,
|
||||||
|
temperature:
|
||||||
|
defaultConfig.temperature ?? baseConfig.temperature,
|
||||||
|
}),
|
||||||
...(isThinkingModel
|
...(isThinkingModel
|
||||||
? {
|
? {
|
||||||
thinkingModel: true,
|
thinkingModel: true,
|
||||||
|
|
@ -247,8 +263,17 @@ export class ModelManager {
|
||||||
|
|
||||||
const fallbackConfig = {
|
const fallbackConfig = {
|
||||||
...fallbackModel,
|
...fallbackModel,
|
||||||
temperature: isThinkingModel ? undefined : baseConfig.temperature,
|
...(fallbackModel.modelName.includes("gpt-5")
|
||||||
maxTokens: baseConfig.maxTokens,
|
? {
|
||||||
|
max_completion_tokens: baseConfig.maxTokens,
|
||||||
|
temperature: 1,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
maxTokens: baseConfig.maxTokens,
|
||||||
|
temperature: isThinkingModel
|
||||||
|
? undefined
|
||||||
|
: baseConfig.temperature,
|
||||||
|
}),
|
||||||
...(isThinkingModel
|
...(isThinkingModel
|
||||||
? {
|
? {
|
||||||
thinkingModel: true,
|
thinkingModel: true,
|
||||||
|
|
@ -331,8 +356,15 @@ export class ModelManager {
|
||||||
return {
|
return {
|
||||||
modelName,
|
modelName,
|
||||||
provider: modelProvider as Provider,
|
provider: modelProvider as Provider,
|
||||||
temperature: taskConfig.temperature,
|
...(modelName.includes("gpt-5")
|
||||||
maxTokens: config.configurable?.maxTokens ?? 10_000,
|
? {
|
||||||
|
max_completion_tokens: config.configurable?.maxTokens ?? 10_000,
|
||||||
|
temperature: 1,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
maxTokens: config.configurable?.maxTokens ?? 10_000,
|
||||||
|
temperature: taskConfig.temperature,
|
||||||
|
}),
|
||||||
thinkingModel,
|
thinkingModel,
|
||||||
thinkingBudgetTokens,
|
thinkingBudgetTokens,
|
||||||
};
|
};
|
||||||
|
|
@ -361,11 +393,11 @@ export class ModelManager {
|
||||||
[LLMTask.SUMMARIZER]: "gemini-2.5-pro",
|
[LLMTask.SUMMARIZER]: "gemini-2.5-pro",
|
||||||
},
|
},
|
||||||
openai: {
|
openai: {
|
||||||
[LLMTask.PLANNER]: "o3",
|
[LLMTask.PLANNER]: "gpt-5",
|
||||||
[LLMTask.PROGRAMMER]: "gpt-4.1",
|
[LLMTask.PROGRAMMER]: "gpt-5",
|
||||||
[LLMTask.REVIEWER]: "o3",
|
[LLMTask.REVIEWER]: "gpt-5",
|
||||||
[LLMTask.ROUTER]: "gpt-4o-mini",
|
[LLMTask.ROUTER]: "gpt-5-nano",
|
||||||
[LLMTask.SUMMARIZER]: "gpt-4.1-mini",
|
[LLMTask.SUMMARIZER]: "gpt-5-mini",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,27 +9,30 @@ import {
|
||||||
import { promises as fs } from "fs";
|
import { promises as fs } from "fs";
|
||||||
import { join, isAbsolute } from "path";
|
import { join, isAbsolute } from "path";
|
||||||
import { GraphConfig } from "@open-swe/shared/open-swe/types";
|
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");
|
const logger = createLogger(LogLevel.INFO, "ReadWriteUtil");
|
||||||
|
|
||||||
async function handleCreateFile(
|
async function handleCreateFile(
|
||||||
sandbox: Sandbox,
|
sandbox: Sandbox | null,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
|
config: GraphConfig,
|
||||||
args?: {
|
args?: {
|
||||||
workDir?: string;
|
workDir?: string;
|
||||||
},
|
},
|
||||||
config?: GraphConfig,
|
|
||||||
) {
|
) {
|
||||||
if (config && isLocalMode(config)) {
|
if (isLocalMode(config)) {
|
||||||
return handleCreateFileLocal(filePath, args?.workDir);
|
return handleCreateFileLocal(filePath, args?.workDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const touchCommand = `touch "${filePath}"`;
|
const executor = createShellExecutor(config);
|
||||||
const touchOutput = await sandbox.process.executeCommand(
|
const touchOutput = await executor.executeCommand({
|
||||||
touchCommand,
|
command: `touch "${filePath}"`,
|
||||||
args?.workDir,
|
workdir: args?.workDir,
|
||||||
);
|
sandbox: sandbox ?? undefined,
|
||||||
|
});
|
||||||
return touchOutput;
|
return touchOutput;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorFields = getSandboxErrorFields(e);
|
const errorFields = getSandboxErrorFields(e);
|
||||||
|
|
@ -49,26 +52,25 @@ async function readFileFunc(inputs: {
|
||||||
sandbox: Sandbox | null;
|
sandbox: Sandbox | null;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
workDir?: string;
|
workDir?: string;
|
||||||
config?: GraphConfig;
|
config: GraphConfig;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
output: string;
|
output: string;
|
||||||
}> {
|
}> {
|
||||||
const { sandbox, filePath, workDir, config } = inputs;
|
const { sandbox, filePath, workDir, config } = inputs;
|
||||||
|
|
||||||
if (config && isLocalMode(config)) {
|
if (isLocalMode(config)) {
|
||||||
return readFileLocal(filePath, workDir);
|
return readFileLocal(filePath, workDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sandbox) {
|
const executor = createShellExecutor(config);
|
||||||
throw new Error("Sandbox is required when not in local mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const readOutput = await sandbox.process.executeCommand(
|
const readOutput = await executor.executeCommand({
|
||||||
`cat "${filePath}"`,
|
command: `cat "${filePath}"`,
|
||||||
workDir,
|
workdir: workDir,
|
||||||
);
|
sandbox: sandbox ?? undefined,
|
||||||
|
});
|
||||||
|
|
||||||
if (readOutput.exitCode !== 0) {
|
if (readOutput.exitCode !== 0) {
|
||||||
const errorResult = readOutput.result ?? readOutput.artifacts?.stdout;
|
const errorResult = readOutput.result ?? readOutput.artifacts?.stdout;
|
||||||
|
|
@ -90,7 +92,7 @@ async function readFileFunc(inputs: {
|
||||||
createOutput = await handleCreateFileLocal(filePath, workDir);
|
createOutput = await handleCreateFileLocal(filePath, workDir);
|
||||||
} else {
|
} else {
|
||||||
// Sandbox mode: use handleCreateFile
|
// Sandbox mode: use handleCreateFile
|
||||||
createOutput = await handleCreateFile(sandbox, filePath, {
|
createOutput = await handleCreateFile(sandbox, filePath, config, {
|
||||||
workDir,
|
workDir,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -164,14 +166,16 @@ async function writeFileFunc(inputs: {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const delimiter = "EOF_" + Date.now() + "_" + Math.random().toString(36);
|
const delimiter = `EOF_${uuidv4()}`;
|
||||||
const writeCommand = `cat > "${filePath}" << '${delimiter}'
|
const writeCommand = `cat > "${filePath}" << '${delimiter}'
|
||||||
${content}
|
${content}
|
||||||
${delimiter}`;
|
${delimiter}`;
|
||||||
const writeOutput = await sandbox.process.executeCommand(
|
const executor = createShellExecutor(config);
|
||||||
writeCommand,
|
const writeOutput = await executor.executeCommand({
|
||||||
workDir,
|
command: writeCommand,
|
||||||
);
|
workdir: workDir,
|
||||||
|
sandbox: sandbox ?? undefined,
|
||||||
|
});
|
||||||
|
|
||||||
if (writeOutput.exitCode !== 0) {
|
if (writeOutput.exitCode !== 0) {
|
||||||
const errorResult = writeOutput.result ?? writeOutput.artifacts?.stdout;
|
const errorResult = writeOutput.result ?? writeOutput.artifacts?.stdout;
|
||||||
|
|
|
||||||
|
|
@ -169,9 +169,9 @@ export async function getSandboxWithErrorHandling(
|
||||||
|
|
||||||
// Get codebase tree
|
// Get codebase tree
|
||||||
const codebaseTree = await getCodebaseTree(
|
const codebaseTree = await getCodebaseTree(
|
||||||
|
config,
|
||||||
sandbox.id,
|
sandbox.id,
|
||||||
targetRepository,
|
targetRepository,
|
||||||
config,
|
|
||||||
);
|
);
|
||||||
const codebaseTreeToReturn =
|
const codebaseTreeToReturn =
|
||||||
codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE ? null : codebaseTree;
|
codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE ? null : codebaseTree;
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ export class ShellExecutor {
|
||||||
env = {},
|
env = {},
|
||||||
timeout = TIMEOUT_SEC,
|
timeout = TIMEOUT_SEC,
|
||||||
sandbox,
|
sandbox,
|
||||||
|
sandboxSessionId,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const commandString = Array.isArray(command) ? command.join(" ") : command;
|
const commandString = Array.isArray(command) ? command.join(" ") : command;
|
||||||
|
|
@ -60,6 +61,7 @@ export class ShellExecutor {
|
||||||
environment,
|
environment,
|
||||||
timeout,
|
timeout,
|
||||||
sandbox,
|
sandbox,
|
||||||
|
sandboxSessionId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,8 +95,13 @@ export class ShellExecutor {
|
||||||
env?: Record<string, string>,
|
env?: Record<string, string>,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
sandbox?: Sandbox,
|
sandbox?: Sandbox,
|
||||||
|
sandboxSessionId?: string,
|
||||||
): Promise<LocalExecuteResponse> {
|
): Promise<LocalExecuteResponse> {
|
||||||
const sandbox_ = sandbox ?? (await getSandboxSessionOrThrow({}));
|
const sandbox_ =
|
||||||
|
sandbox ??
|
||||||
|
(await getSandboxSessionOrThrow({
|
||||||
|
xSandboxSessionId: sandboxSessionId,
|
||||||
|
}));
|
||||||
|
|
||||||
return await sandbox_.process.executeCommand(
|
return await sandbox_.process.executeCommand(
|
||||||
command,
|
command,
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,5 @@ export interface ExecuteCommandOptions {
|
||||||
env?: Record<string, string>;
|
env?: Record<string, string>;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
sandbox?: Sandbox;
|
sandbox?: Sandbox;
|
||||||
|
sandboxSessionId?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import {
|
||||||
TargetRepository,
|
TargetRepository,
|
||||||
GraphConfig,
|
GraphConfig,
|
||||||
} from "@open-swe/shared/open-swe/types";
|
} from "@open-swe/shared/open-swe/types";
|
||||||
import { daytonaClient } from "./sandbox.js";
|
|
||||||
import { createLogger, LogLevel } from "./logger.js";
|
import { createLogger, LogLevel } from "./logger.js";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { SANDBOX_ROOT_DIR, TIMEOUT_SEC } from "@open-swe/shared/constants";
|
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.";
|
"Failed to generate tree. Please try again.";
|
||||||
|
|
||||||
export async function getCodebaseTree(
|
export async function getCodebaseTree(
|
||||||
|
config: GraphConfig,
|
||||||
sandboxSessionId_?: string,
|
sandboxSessionId_?: string,
|
||||||
targetRepository_?: TargetRepository,
|
targetRepository_?: TargetRepository,
|
||||||
config?: GraphConfig,
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const command = `git ls-files | tree --fromfile -L 3`;
|
const command = `git ls-files | tree --fromfile -L 3`;
|
||||||
|
|
@ -28,7 +27,7 @@ export async function getCodebaseTree(
|
||||||
let targetRepository = targetRepository_;
|
let targetRepository = targetRepository_;
|
||||||
|
|
||||||
// Check if we're in local mode
|
// Check if we're in local mode
|
||||||
if (config && isLocalMode(config)) {
|
if (isLocalMode(config)) {
|
||||||
return getCodebaseTreeLocal(config);
|
return getCodebaseTreeLocal(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,14 +52,14 @@ export async function getCodebaseTree(
|
||||||
throw new Error("Failed generate tree: No target repository provided");
|
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 repoDir = path.join(SANDBOX_ROOT_DIR, targetRepository.repo);
|
||||||
const response = await sandbox.process.executeCommand(
|
const response = await executor.executeCommand({
|
||||||
command,
|
command,
|
||||||
repoDir,
|
workdir: repoDir,
|
||||||
undefined,
|
timeout: TIMEOUT_SEC,
|
||||||
TIMEOUT_SEC,
|
sandboxSessionId,
|
||||||
);
|
});
|
||||||
|
|
||||||
if (response.exitCode !== 0) {
|
if (response.exitCode !== 0) {
|
||||||
logger.error("Failed to generate tree", {
|
logger.error("Failed to generate tree", {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,22 @@
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
export function wrapScript(command: string): string {
|
export function wrapScript(command: string): string {
|
||||||
// Use bash directly to avoid script command compatibility issues
|
const makeDelim = () => `OPEN_SWE_${uuidv4()}`;
|
||||||
return `bash -c "$(cat <<'OPEN_SWE_X'
|
|
||||||
|
// 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}
|
${command}
|
||||||
OPEN_SWE_X
|
${delim}
|
||||||
)"`;
|
)" /dev/null`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,18 @@ export const MODEL_OPTIONS = [
|
||||||
label: "Claude 3.5 Haiku",
|
label: "Claude 3.5 Haiku",
|
||||||
value: "anthropic:claude-3-5-haiku-latest",
|
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",
|
label: "o4",
|
||||||
value: "openai:o4",
|
value: "openai:o4",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue