mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-04 16:02:13 +00:00
fix: Better daytona error handling (#429)
This commit is contained in:
parent
13329fe5c1
commit
d4ca73089c
4 changed files with 135 additions and 33 deletions
|
|
@ -22,7 +22,10 @@ import {
|
||||||
import { Command } from "@langchain/langgraph";
|
import { Command } from "@langchain/langgraph";
|
||||||
import { truncateOutput } from "../../../utils/truncate-outputs.js";
|
import { truncateOutput } from "../../../utils/truncate-outputs.js";
|
||||||
import { getSandboxWithErrorHandling } from "../../../utils/sandbox.js";
|
import { getSandboxWithErrorHandling } from "../../../utils/sandbox.js";
|
||||||
import { getCodebaseTree } from "../../../utils/tree.js";
|
import {
|
||||||
|
FAILED_TO_GENERATE_TREE_MESSAGE,
|
||||||
|
getCodebaseTree,
|
||||||
|
} from "../../../utils/tree.js";
|
||||||
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
||||||
import { createInstallDependenciesTool } from "../../../tools/install-dependencies.js";
|
import { createInstallDependenciesTool } from "../../../tools/install-dependencies.js";
|
||||||
import { createSearchTool } from "../../../tools/search.js";
|
import { createSearchTool } from "../../../tools/search.js";
|
||||||
|
|
@ -180,6 +183,11 @@ export async function takeAction(
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const codebaseTree = await getCodebaseTree();
|
const codebaseTree = await getCodebaseTree();
|
||||||
|
// 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
|
||||||
|
? (state.codebaseTree ?? codebaseTree)
|
||||||
|
: codebaseTree;
|
||||||
|
|
||||||
// Prioritize wereDependenciesInstalled over dependenciesInstalled
|
// Prioritize wereDependenciesInstalled over dependenciesInstalled
|
||||||
const dependenciesInstalledUpdate =
|
const dependenciesInstalledUpdate =
|
||||||
|
|
@ -193,7 +201,7 @@ export async function takeAction(
|
||||||
messages: toolCallResults,
|
messages: toolCallResults,
|
||||||
internalMessages: toolCallResults,
|
internalMessages: toolCallResults,
|
||||||
...(branchName && { branchName }),
|
...(branchName && { branchName }),
|
||||||
codebaseTree,
|
codebaseTree: codebaseTreeToReturn,
|
||||||
sandboxSessionId: sandbox.id,
|
sandboxSessionId: sandbox.id,
|
||||||
...(dependenciesInstalledUpdate !== null && {
|
...(dependenciesInstalledUpdate !== null && {
|
||||||
dependenciesInstalled: dependenciesInstalledUpdate,
|
dependenciesInstalled: dependenciesInstalledUpdate,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,10 @@ import {
|
||||||
configureGitUserInRepo,
|
configureGitUserInRepo,
|
||||||
pullLatestChanges,
|
pullLatestChanges,
|
||||||
} from "../../utils/github/git.js";
|
} from "../../utils/github/git.js";
|
||||||
import { getCodebaseTree } from "../../utils/tree.js";
|
import {
|
||||||
|
FAILED_TO_GENERATE_TREE_MESSAGE,
|
||||||
|
getCodebaseTree,
|
||||||
|
} from "../../utils/tree.js";
|
||||||
import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants";
|
import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants";
|
||||||
import {
|
import {
|
||||||
CustomNodeEvent,
|
CustomNodeEvent,
|
||||||
|
|
@ -171,7 +174,15 @@ export async function initializeSandbox(
|
||||||
emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
|
emitStepEvent(baseGenerateCodebaseTreeAction, "pending");
|
||||||
try {
|
try {
|
||||||
const codebaseTree = await getCodebaseTree(existingSandbox.id);
|
const codebaseTree = await getCodebaseTree(existingSandbox.id);
|
||||||
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
|
if (codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE) {
|
||||||
|
emitStepEvent(
|
||||||
|
baseGenerateCodebaseTreeAction,
|
||||||
|
"error",
|
||||||
|
FAILED_TO_GENERATE_TREE_MESSAGE,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
emitStepEvent(baseGenerateCodebaseTreeAction, "success");
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sandboxSessionId: existingSandbox.id,
|
sandboxSessionId: existingSandbox.id,
|
||||||
|
|
@ -183,9 +194,14 @@ export async function initializeSandbox(
|
||||||
emitStepEvent(
|
emitStepEvent(
|
||||||
baseGenerateCodebaseTreeAction,
|
baseGenerateCodebaseTreeAction,
|
||||||
"error",
|
"error",
|
||||||
"Failed to generate codebase tree. Please try again later.",
|
FAILED_TO_GENERATE_TREE_MESSAGE,
|
||||||
);
|
);
|
||||||
throw new Error("Failed to generate codebase tree.");
|
return {
|
||||||
|
sandboxSessionId: existingSandbox.id,
|
||||||
|
codebaseTree: FAILED_TO_GENERATE_TREE_MESSAGE,
|
||||||
|
messages: createEventsMessage(),
|
||||||
|
customRules: await getCustomRules(existingSandbox, absoluteRepoDir),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
emitStepEvent(
|
emitStepEvent(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { DEFAULT_SANDBOX_CREATE_PARAMS } from "../constants.js";
|
||||||
import { getGitHubTokensFromConfig } from "./github-tokens.js";
|
import { getGitHubTokensFromConfig } from "./github-tokens.js";
|
||||||
import { cloneRepo, configureGitUserInRepo } from "./github/git.js";
|
import { cloneRepo, configureGitUserInRepo } from "./github/git.js";
|
||||||
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
||||||
import { getCodebaseTree } from "./tree.js";
|
import { FAILED_TO_GENERATE_TREE_MESSAGE, getCodebaseTree } from "./tree.js";
|
||||||
|
|
||||||
const logger = createLogger(LogLevel.INFO, "Sandbox");
|
const logger = createLogger(LogLevel.INFO, "Sandbox");
|
||||||
|
|
||||||
|
|
@ -65,6 +65,28 @@ export async function deleteSandbox(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createSandbox(attempt: number): Promise<Sandbox | null> {
|
||||||
|
try {
|
||||||
|
return await daytonaClient().create(DEFAULT_SANDBOX_CREATE_PARAMS, {
|
||||||
|
timeout: 100, // 100s timeout on creation.
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Failed to create sandbox", {
|
||||||
|
attempt,
|
||||||
|
...(e instanceof Error
|
||||||
|
? {
|
||||||
|
name: e.name,
|
||||||
|
message: e.message,
|
||||||
|
stack: e.stack,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
error: e,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSandboxWithErrorHandling(
|
export async function getSandboxWithErrorHandling(
|
||||||
sandboxSessionId: string | undefined,
|
sandboxSessionId: string | undefined,
|
||||||
targetRepository: TargetRepository,
|
targetRepository: TargetRepository,
|
||||||
|
|
@ -112,7 +134,19 @@ export async function getSandboxWithErrorHandling(
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sandbox = await daytonaClient().create(DEFAULT_SANDBOX_CREATE_PARAMS);
|
let sandbox: Sandbox | null = null;
|
||||||
|
let numSandboxCreateAttempts = 0;
|
||||||
|
while (!sandbox && numSandboxCreateAttempts < 3) {
|
||||||
|
sandbox = await createSandbox(numSandboxCreateAttempts);
|
||||||
|
if (!sandbox) {
|
||||||
|
numSandboxCreateAttempts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sandbox) {
|
||||||
|
throw new Error("Failed to create sandbox after 3 attempts");
|
||||||
|
}
|
||||||
|
|
||||||
const { githubInstallationToken } = getGitHubTokensFromConfig(config);
|
const { githubInstallationToken } = getGitHubTokensFromConfig(config);
|
||||||
|
|
||||||
// Clone repository
|
// Clone repository
|
||||||
|
|
@ -130,14 +164,16 @@ export async function getSandboxWithErrorHandling(
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get codebase tree
|
// Get codebase tree
|
||||||
const codebaseTree = await getCodebaseTree(sandbox.id);
|
const codebaseTree = await getCodebaseTree(sandbox.id, targetRepository);
|
||||||
|
const codebaseTreeToReturn =
|
||||||
|
codebaseTree === FAILED_TO_GENERATE_TREE_MESSAGE ? null : codebaseTree;
|
||||||
|
|
||||||
logger.info("Sandbox created successfully", {
|
logger.info("Sandbox created successfully", {
|
||||||
sandboxId: sandbox.id,
|
sandboxId: sandbox.id,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
sandbox,
|
sandbox,
|
||||||
codebaseTree,
|
codebaseTree: codebaseTreeToReturn,
|
||||||
dependenciesInstalled: false,
|
dependenciesInstalled: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,78 @@
|
||||||
import { getCurrentTaskInput } from "@langchain/langgraph";
|
import { getCurrentTaskInput } from "@langchain/langgraph";
|
||||||
import { GraphState } from "@open-swe/shared/open-swe/types";
|
import { GraphState, TargetRepository } from "@open-swe/shared/open-swe/types";
|
||||||
import { daytonaClient } from "./sandbox.js";
|
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";
|
||||||
|
import { getSandboxErrorFields } from "./sandbox-error-fields.js";
|
||||||
|
|
||||||
const logger = createLogger(LogLevel.INFO, "Tree");
|
const logger = createLogger(LogLevel.INFO, "Tree");
|
||||||
|
|
||||||
export async function getCodebaseTree(sandboxSessionId_?: string) {
|
export const FAILED_TO_GENERATE_TREE_MESSAGE =
|
||||||
const command = `git ls-files | tree --fromfile -L 3`;
|
"Failed to generate tree. Please try again.";
|
||||||
const state = getCurrentTaskInput<GraphState>();
|
|
||||||
const sandboxSessionId = sandboxSessionId_ || state.sandboxSessionId;
|
|
||||||
if (!sandboxSessionId) {
|
|
||||||
logger.error("Failed to generate tree: No sandbox session ID provided");
|
|
||||||
throw new Error("Failed generate tree: No sandbox session ID provided");
|
|
||||||
}
|
|
||||||
const sandbox = await daytonaClient().get(sandboxSessionId);
|
|
||||||
const repoDir = path.join(SANDBOX_ROOT_DIR, state.targetRepository.repo);
|
|
||||||
const response = await sandbox.process.executeCommand(
|
|
||||||
command,
|
|
||||||
repoDir,
|
|
||||||
undefined,
|
|
||||||
TIMEOUT_SEC,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.exitCode !== 0) {
|
export async function getCodebaseTree(
|
||||||
|
sandboxSessionId_?: string,
|
||||||
|
targetRepository_?: TargetRepository,
|
||||||
|
): Promise<string> {
|
||||||
|
try {
|
||||||
|
const command = `git ls-files | tree --fromfile -L 3`;
|
||||||
|
let sandboxSessionId = sandboxSessionId_;
|
||||||
|
let targetRepository = targetRepository_;
|
||||||
|
|
||||||
|
// If sandbox session ID is not provided, try to get it from the current state.
|
||||||
|
if (!sandboxSessionId || !targetRepository) {
|
||||||
|
try {
|
||||||
|
const state = getCurrentTaskInput<GraphState>();
|
||||||
|
// Prefer the provided sandbox session ID and target repository. Fallback to state if defined.
|
||||||
|
sandboxSessionId = sandboxSessionId ?? state.sandboxSessionId;
|
||||||
|
targetRepository = targetRepository ?? state.targetRepository;
|
||||||
|
} catch {
|
||||||
|
// not executed in a LangGraph instance. continue.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sandboxSessionId) {
|
||||||
|
logger.error("Failed to generate tree: No sandbox session ID provided");
|
||||||
|
throw new Error("Failed generate tree: No sandbox session ID provided");
|
||||||
|
}
|
||||||
|
if (!targetRepository) {
|
||||||
|
logger.error("Failed to generate tree: No target repository provided");
|
||||||
|
throw new Error("Failed generate tree: No target repository provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
const sandbox = await daytonaClient().get(sandboxSessionId);
|
||||||
|
const repoDir = path.join(SANDBOX_ROOT_DIR, targetRepository.repo);
|
||||||
|
const response = await sandbox.process.executeCommand(
|
||||||
|
command,
|
||||||
|
repoDir,
|
||||||
|
undefined,
|
||||||
|
TIMEOUT_SEC,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.exitCode !== 0) {
|
||||||
|
logger.error("Failed to generate tree", {
|
||||||
|
exitCode: response.exitCode,
|
||||||
|
result: response.result ?? response.artifacts?.stdout,
|
||||||
|
});
|
||||||
|
throw new Error(
|
||||||
|
`Failed to generate tree: ${response.result ?? response.artifacts?.stdout}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.result;
|
||||||
|
} catch (e) {
|
||||||
|
const errorFields = getSandboxErrorFields(e);
|
||||||
logger.error("Failed to generate tree", {
|
logger.error("Failed to generate tree", {
|
||||||
error: response.result,
|
...(errorFields ? { errorFields } : {}),
|
||||||
error_result: response,
|
...(e instanceof Error
|
||||||
|
? {
|
||||||
|
name: e.name,
|
||||||
|
message: e.message,
|
||||||
|
stack: e.stack,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
});
|
});
|
||||||
throw new Error(`Failed to generate tree: ${response.result}`);
|
return FAILED_TO_GENERATE_TREE_MESSAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.result;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue