mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 09:13:14 +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">;
|
||||
try {
|
||||
const jsonReportResult = await readFile({
|
||||
config: {},
|
||||
sandbox,
|
||||
filePath: "/tmp/pytest_report.json",
|
||||
workDir: repoDir,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -15,4 +15,5 @@ export interface ExecuteCommandOptions {
|
|||
env?: Record<string, string>;
|
||||
timeout?: number;
|
||||
sandbox?: Sandbox;
|
||||
sandboxSessionId?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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", {
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue