mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 11:33:14 +00:00
feat: Optionally create gh issue from requests (#781)
* feat: Optionally create gh issue from requests * cr * cr * cr * cr * docs
This commit is contained in:
parent
c42638f9a3
commit
f316685de3
24 changed files with 261 additions and 355 deletions
|
|
@ -16,6 +16,15 @@ description: Frequently Asked Questions
|
|||
Yes. When using Anthropic models, all input tokens are cached on Anthropic's servers.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Can I disable Open SWE from creating an issue when I submit a request?">
|
||||
Yes. There's two ways to disable Open SWE from creating an issue when you submit a request:
|
||||
|
||||
1. Toggle the 'eye' icon in the main chat area when submitting a request.
|
||||
2. In the configuration tab in settings, toggle the 'Should Create Issue' switch.
|
||||
|
||||
By default, it's set to `true`. By modifying this setting in the configuration tab, all runs will default to that setting. You may override this setting on a per-run basis by toggling the 'eye' icon in the main chat area.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="My run failed midway through. What now?">
|
||||
We're sorry you're experiencing this! Open SWE will automatically commit any changes it makes to a draft pull request. Please check the draft pull request and make any necessary changes.
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import { isLocalMode } from "@open-swe/shared/open-swe/local-mode";
|
|||
import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types";
|
||||
import { GraphState } from "@open-swe/shared/open-swe/types";
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
import { shouldCreateIssue } from "../../../../utils/should-create-issue.js";
|
||||
const logger = createLogger(LogLevel.INFO, "ClassifyMessage");
|
||||
|
||||
/**
|
||||
|
|
@ -186,6 +187,39 @@ export async function classifyMessage(
|
|||
);
|
||||
}
|
||||
|
||||
if (!shouldCreateIssue(config)) {
|
||||
const commandUpdate: ManagerGraphUpdate = {
|
||||
messages: [response],
|
||||
};
|
||||
if (
|
||||
toolCallArgs.route === "start_planner" ||
|
||||
toolCallArgs.route === "start_planner_for_followup"
|
||||
) {
|
||||
return new Command({
|
||||
update: commandUpdate,
|
||||
goto: "start-planner",
|
||||
});
|
||||
}
|
||||
|
||||
if (toolCallArgs.route === "create_new_issue") {
|
||||
return new Command({
|
||||
update: commandUpdate,
|
||||
goto: "create-new-session",
|
||||
});
|
||||
}
|
||||
|
||||
if (toolCallArgs.route === "no_op") {
|
||||
return new Command({
|
||||
update: commandUpdate,
|
||||
goto: END,
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Unsupported route received: ${toolCallArgs.route}\nUnable to route message there when not creating GitHub issues for request.`,
|
||||
);
|
||||
}
|
||||
|
||||
const { githubAccessToken } = getGitHubTokensFromConfig(config);
|
||||
let githubIssueId = state.githubIssueId;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { StreamMode } from "@langchain/langgraph-sdk";
|
|||
import { isLocalMode } from "@open-swe/shared/open-swe/local-mode";
|
||||
import { regenerateInstallationToken } from "../../../utils/github/regenerate-token.js";
|
||||
import { createLogger, LogLevel } from "../../../utils/logger.js";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "CreateNewSession");
|
||||
|
||||
|
|
@ -48,16 +49,21 @@ export async function createNewSession(
|
|||
state.messages,
|
||||
config.configurable,
|
||||
);
|
||||
const { githubAccessToken } = getGitHubTokensFromConfig(config);
|
||||
const newIssue = await createIssue({
|
||||
owner: state.targetRepository.owner,
|
||||
repo: state.targetRepository.repo,
|
||||
title: titleAndContent.title,
|
||||
body: formatContentForIssueBody(titleAndContent.body),
|
||||
githubAccessToken,
|
||||
});
|
||||
if (!newIssue) {
|
||||
throw new Error("Failed to create new issue");
|
||||
|
||||
let newIssueNumber: number | undefined;
|
||||
if (shouldCreateIssue(config)) {
|
||||
const { githubAccessToken } = getGitHubTokensFromConfig(config);
|
||||
const newIssue = await createIssue({
|
||||
owner: state.targetRepository.owner,
|
||||
repo: state.targetRepository.repo,
|
||||
title: titleAndContent.title,
|
||||
body: formatContentForIssueBody(titleAndContent.body),
|
||||
githubAccessToken,
|
||||
});
|
||||
if (!newIssue) {
|
||||
throw new Error("Failed to create new issue");
|
||||
}
|
||||
newIssueNumber = newIssue.number;
|
||||
}
|
||||
|
||||
const inputMessages: BaseMessage[] = [
|
||||
|
|
@ -71,7 +77,7 @@ ${ISSUE_CONTENT_OPEN_TAG}
|
|||
${titleAndContent.body}
|
||||
${ISSUE_CONTENT_CLOSE_TAG}`,
|
||||
additional_kwargs: {
|
||||
githubIssueId: newIssue.number,
|
||||
githubIssueId: newIssueNumber,
|
||||
isOriginalIssue: true,
|
||||
},
|
||||
}),
|
||||
|
|
@ -102,7 +108,7 @@ ${ISSUE_CONTENT_CLOSE_TAG}`,
|
|||
|
||||
const newManagerThreadId = uuidv4();
|
||||
const commandUpdate: ManagerGraphUpdate = {
|
||||
githubIssueId: newIssue.number,
|
||||
githubIssueId: newIssueNumber,
|
||||
targetRepository: state.targetRepository,
|
||||
messages: inputMessages,
|
||||
branchName: state.branchName ?? getBranchName(config),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { getCustomConfigurableFields } from "../../../utils/config.js";
|
|||
import { getRecentUserRequest } from "../../../utils/user-request.js";
|
||||
import { StreamMode } from "@langchain/langgraph-sdk";
|
||||
import { regenerateInstallationToken } from "../../../utils/github/regenerate-token.js";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "StartPlanner");
|
||||
|
||||
|
|
@ -68,6 +69,9 @@ export async function startPlanner(
|
|||
branchName: state.branchName ?? getBranchName(config),
|
||||
autoAcceptPlan: state.autoAcceptPlan,
|
||||
...(followupMessage || localMode ? { messages: [followupMessage] } : {}),
|
||||
...(!shouldCreateIssue(config) && followupMessage
|
||||
? { internalMessages: [followupMessage] }
|
||||
: {}),
|
||||
};
|
||||
|
||||
const run = await langGraphClient.runs.create(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { filterHiddenMessages } from "../../../utils/message/filter-hidden.js";
|
|||
import { createLogger, LogLevel } from "../../../utils/logger.js";
|
||||
import { trackCachePerformance } from "../../../utils/caching.js";
|
||||
import { getModelManager } from "../../../utils/llms/model-manager.js";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "DetermineNeedsContext");
|
||||
|
||||
|
|
@ -118,7 +119,7 @@ export async function determineNeedsContext(
|
|||
config: GraphConfig,
|
||||
): Promise<Command> {
|
||||
const [missingMessages, model] = await Promise.all([
|
||||
getMissingMessages(state, config),
|
||||
shouldCreateIssue(config) ? getMissingMessages(state, config) : [],
|
||||
loadModel(config, LLMTask.ROUTER),
|
||||
]);
|
||||
const modelManager = getModelManager();
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import {
|
|||
trackCachePerformance,
|
||||
} from "../../../../utils/caching.js";
|
||||
import { createViewTool } from "../../../../tools/builtin-tools/view.js";
|
||||
import { shouldCreateIssue } from "../../../../utils/should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "GeneratePlanningMessageNode");
|
||||
|
||||
|
|
@ -124,10 +125,14 @@ export async function generateAction(
|
|||
: {}),
|
||||
});
|
||||
|
||||
const [missingMessages, { taskPlan: latestTaskPlan }] = await Promise.all([
|
||||
getMissingMessages(state, config),
|
||||
getPlansFromIssue(state, config),
|
||||
]);
|
||||
const [missingMessages, { taskPlan: latestTaskPlan }] = shouldCreateIssue(
|
||||
config,
|
||||
)
|
||||
? await Promise.all([
|
||||
getMissingMessages(state, config),
|
||||
getPlansFromIssue(state, config),
|
||||
])
|
||||
: [[], { taskPlan: null }];
|
||||
|
||||
const inputMessages = filterMessagesWithoutContent([
|
||||
...state.messages,
|
||||
|
|
|
|||
|
|
@ -21,24 +21,27 @@ import {
|
|||
import { filterHiddenMessages } from "../../../utils/message/filter-hidden.js";
|
||||
import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants";
|
||||
import { isLocalMode } from "@open-swe/shared/open-swe/local-mode";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
|
||||
export async function prepareGraphState(
|
||||
state: PlannerGraphState,
|
||||
config: GraphConfig,
|
||||
): Promise<Command> {
|
||||
if (isLocalMode(config)) {
|
||||
// In local mode, just proceed to initialize-sandbox with existing messages
|
||||
if (isLocalMode(config) || !shouldCreateIssue(config)) {
|
||||
return new Command({
|
||||
update: {},
|
||||
goto: "initialize-sandbox",
|
||||
});
|
||||
}
|
||||
|
||||
if (!state.githubIssueId) {
|
||||
throw new Error("No github issue id provided");
|
||||
}
|
||||
|
||||
if (!state.targetRepository) {
|
||||
throw new Error("No target repository provided");
|
||||
}
|
||||
|
||||
const { githubInstallationToken } = getGitHubTokensFromConfig(config);
|
||||
const baseGetIssueInputs = {
|
||||
owner: state.targetRepository.owner,
|
||||
|
|
@ -57,8 +60,6 @@ export async function prepareGraphState(
|
|||
throw new Error(`Issue not found. Issue ID: ${state.githubIssueId}`);
|
||||
}
|
||||
|
||||
// Ensure the main issue & all comments are included in the state;
|
||||
|
||||
// If the messages state is empty, we can just include all comments as human messages.
|
||||
if (!state.messages?.length) {
|
||||
const commandUpdate: PlannerGraphUpdate = {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import {
|
|||
cleanTaskItems,
|
||||
} from "../../../utils/github/plan.js";
|
||||
import { regenerateInstallationToken } from "../../../utils/github/regenerate-token.js";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "ProposedPlan");
|
||||
|
||||
|
|
@ -149,7 +150,7 @@ async function startProgrammerRun(input: {
|
|||
);
|
||||
|
||||
// Skip GitHub operations in local mode
|
||||
if (!isLocalMode(config)) {
|
||||
if (!isLocalMode(config) && shouldCreateIssue(config)) {
|
||||
await addTaskPlanToIssue(
|
||||
{
|
||||
githubIssueId: state.githubIssueId,
|
||||
|
|
|
|||
|
|
@ -1,288 +0,0 @@
|
|||
// TODO: NOT HOOKED UP TO THE GRAPH YET
|
||||
// TODO: WILL NEED TO REFACTOR TO ALLOW FOR CHATTING WITH PLANNING SUBGRAPH
|
||||
|
||||
import { GraphConfig, PlanItem } from "@open-swe/shared/open-swe/types";
|
||||
import { z } from "zod";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { traceable } from "langsmith/traceable";
|
||||
import {
|
||||
PlannerGraphState,
|
||||
PlannerGraphUpdate,
|
||||
} from "@open-swe/shared/open-swe/planner/types";
|
||||
import {
|
||||
getInitialUserRequest,
|
||||
getRecentUserRequest,
|
||||
} from "../../../utils/user-request.js";
|
||||
import {
|
||||
loadModel,
|
||||
supportsParallelToolCallsParam,
|
||||
} from "../../../utils/llms/index.js";
|
||||
import { LLMTask } from "@open-swe/shared/open-swe/llm-task";
|
||||
import { FallbackRunnable } from "../../../utils/runtime-fallback.js";
|
||||
|
||||
const systemPromptIdentifyChanges = `You are operating as an agentic coding assistant built by LangChain. You've previously been given a task to generate a plan of action for, to address the user's initial request.
|
||||
|
||||
Here is the user's initial request:
|
||||
{USER_INITIAL_REQUEST}
|
||||
|
||||
After generating that plan, the user has submitted some feedback/change requests. You should now identify exactly which tasks in the plan should be modified based on their request.
|
||||
|
||||
Here is their request:
|
||||
{USER_REQUEST}
|
||||
|
||||
The plan you generated originally, which they submitted the above request for is as follows:
|
||||
{PLAN}
|
||||
|
||||
Please read over the generated plan, and the user's request, and identify exactly which tasks in the plan should be modified/removed. Call the 'identify_plan_changes' tool and use the indices of the tasks listed above when calling the tool.`;
|
||||
|
||||
const systemPrompt = `You are operating as an agentic coding assistant built by LangChain. You've previously been given a task to generate a plan of action for, to address the user's initial request.
|
||||
|
||||
In this step, the user has requested you rewrite/modify parts of a high-level plan. You have already identified the specific tasks in the plan that should be modified/removed based on the user's request.
|
||||
|
||||
Here is the user's initial request which you used to generate the initial plan:
|
||||
{USER_INITIAL_REQUEST}
|
||||
|
||||
Here is the full plan you generated:
|
||||
{PLAN}
|
||||
|
||||
Here is the request the user has just made which you should use to rewrite/modify the plan:
|
||||
{USER_REQUEST}
|
||||
|
||||
And here are the specific tasks in the plan which were identified as tasks the user wants to modify/remove:
|
||||
{TASKS_TO_MODIFY}
|
||||
|
||||
Given this context, please address the user's request to modify/remove/add the tasks in the plan.
|
||||
|
||||
You MUST adhere to the following criteria when generating the plan:
|
||||
- Make as few changes as possible to the tasks, while still following the users request.
|
||||
- You should NOT make ANY changes to the tasks in the plan that are NOT listed as tasks to modify/remove.
|
||||
- Do NOT modify tasks in the plan not listed as tasks to modify/remove.
|
||||
- When responding, ensure you include the unmodified tasks in the plan, as well as the modified/new tasks.
|
||||
- To remove a specific task, simply do NOT include it in the response.
|
||||
- To add a new task, simply include it in the response.
|
||||
`;
|
||||
|
||||
const formatSysPromptIdentifyTasks = (
|
||||
userInitialRequest: string,
|
||||
userRequest: string,
|
||||
previousPlan: string[],
|
||||
) => {
|
||||
return systemPromptIdentifyChanges
|
||||
.replace("{USER_INITIAL_REQUEST}", userInitialRequest)
|
||||
.replace("{USER_REQUEST}", userRequest)
|
||||
.replace(
|
||||
"{PLAN}",
|
||||
previousPlan.map((plan, index) => `${index}: ${plan}`).join("\n"),
|
||||
);
|
||||
};
|
||||
|
||||
const formatSysPromptRewritePlan = (
|
||||
userInitialRequest: string,
|
||||
userRequest: string,
|
||||
previousPlan: string[],
|
||||
tasksToModify: PlanItem[],
|
||||
) => {
|
||||
return systemPrompt
|
||||
.replace("{USER_INITIAL_REQUEST}", userInitialRequest)
|
||||
.replace("{USER_REQUEST}", userRequest)
|
||||
.replace(
|
||||
"{PLAN}",
|
||||
previousPlan.map((plan, index) => `${index}: ${plan}`).join("\n"),
|
||||
)
|
||||
.replace(
|
||||
"{TASKS_TO_MODIFY}",
|
||||
tasksToModify.map((p) => `${p.index}: ${p.plan}`).join("\n"),
|
||||
);
|
||||
};
|
||||
|
||||
async function identifyTasksToModifyFunc(
|
||||
state: PlannerGraphState,
|
||||
model: FallbackRunnable,
|
||||
supportsParallelToolCallsParam: boolean,
|
||||
): Promise<PlanItem[]> {
|
||||
if (!state.planChangeRequest) {
|
||||
throw new Error("No plan change request found.");
|
||||
}
|
||||
|
||||
const identifyPlanChangesSchema = z.object({
|
||||
task_change_indices: z
|
||||
.array(z.number())
|
||||
.describe(
|
||||
"The indices of the tasks in the plan that should be modified/removed.",
|
||||
),
|
||||
});
|
||||
|
||||
const identifyPlanChangesTool = tool(
|
||||
(input): PlanItem[] => {
|
||||
const { task_change_indices } = input;
|
||||
const tasksToModify = state.proposedPlan.flatMap((plan, planIndex) => {
|
||||
const planItem = task_change_indices.some(
|
||||
(changeIndex) => changeIndex === planIndex,
|
||||
);
|
||||
if (!planItem) {
|
||||
return [];
|
||||
}
|
||||
return {
|
||||
index: planIndex,
|
||||
plan: plan,
|
||||
completed: false,
|
||||
};
|
||||
});
|
||||
|
||||
return tasksToModify;
|
||||
},
|
||||
{
|
||||
name: "identify_plan_changes",
|
||||
schema: identifyPlanChangesSchema,
|
||||
description:
|
||||
"Identify which tasks in the plan should be modified/removed based on the user's request.",
|
||||
},
|
||||
);
|
||||
|
||||
const modelWithIdentifyChangesTool = model.bindTools(
|
||||
[identifyPlanChangesTool],
|
||||
{
|
||||
// The model should always call the tool when identifying plan changes.
|
||||
tool_choice: identifyPlanChangesTool.name,
|
||||
...(supportsParallelToolCallsParam
|
||||
? {
|
||||
parallel_tool_calls: false,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
);
|
||||
|
||||
const userInitialRequest = getInitialUserRequest(state.messages);
|
||||
const userFollowupRequest = getRecentUserRequest(state.messages);
|
||||
const userRequest =
|
||||
userFollowupRequest ?? userInitialRequest ?? "No user message found";
|
||||
|
||||
const response = await modelWithIdentifyChangesTool.invoke([
|
||||
{
|
||||
role: "user",
|
||||
content: formatSysPromptIdentifyTasks(
|
||||
userRequest,
|
||||
state.planChangeRequest,
|
||||
state.proposedPlan,
|
||||
),
|
||||
},
|
||||
]);
|
||||
|
||||
const toolCall = response.tool_calls?.[0];
|
||||
if (!toolCall) {
|
||||
throw new Error(
|
||||
"Tool call not returned when attempting to identify plan changes.",
|
||||
);
|
||||
}
|
||||
|
||||
const tasksToModify = await identifyPlanChangesTool.invoke(
|
||||
toolCall.args as z.infer<typeof identifyPlanChangesSchema>,
|
||||
);
|
||||
return tasksToModify;
|
||||
}
|
||||
|
||||
const identifyTasksToModify = traceable(identifyTasksToModifyFunc, {
|
||||
name: "identify_tasks_to_modify",
|
||||
});
|
||||
|
||||
async function updatePlanTasksFunc(
|
||||
state: PlannerGraphState,
|
||||
tasksToModify: PlanItem[],
|
||||
model: FallbackRunnable,
|
||||
supportsParallelToolCallsParam: boolean,
|
||||
): Promise<string[]> {
|
||||
if (!state.planChangeRequest) {
|
||||
throw new Error("No plan change request found.");
|
||||
}
|
||||
|
||||
const updatePlanTasksSchema = z.object({
|
||||
updated_plan_tasks: z
|
||||
.array(
|
||||
z
|
||||
.string()
|
||||
.describe(
|
||||
"The updated or unmodified plan for the task. Do NOT include the task index.",
|
||||
),
|
||||
)
|
||||
.describe(
|
||||
"The updated plan tasks. Must be in the order of which they should be executed in.",
|
||||
),
|
||||
});
|
||||
const updatePlanTasksTool = {
|
||||
name: "update_plan_tasks",
|
||||
description: "Call this tool to respond with the updated plan.",
|
||||
schema: updatePlanTasksSchema,
|
||||
};
|
||||
|
||||
const modelWithUpdatePlanTasksTool = model.bindTools([updatePlanTasksTool], {
|
||||
// The model should always call the tool when identifying plan changes.
|
||||
tool_choice: updatePlanTasksTool.name,
|
||||
...(supportsParallelToolCallsParam
|
||||
? {
|
||||
parallel_tool_calls: false,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
const userInitialRequest = getInitialUserRequest(state.messages);
|
||||
const userFollowupRequest = getRecentUserRequest(state.messages);
|
||||
const userRequest =
|
||||
userFollowupRequest ?? userInitialRequest ?? "No user message found";
|
||||
|
||||
const response = await modelWithUpdatePlanTasksTool.invoke([
|
||||
{
|
||||
role: "user",
|
||||
content: formatSysPromptRewritePlan(
|
||||
userRequest,
|
||||
state.planChangeRequest,
|
||||
state.proposedPlan,
|
||||
tasksToModify,
|
||||
),
|
||||
},
|
||||
]);
|
||||
|
||||
const toolCall = response.tool_calls?.[0];
|
||||
if (!toolCall) {
|
||||
throw new Error(
|
||||
"Tool call not returned when attempting to update plan tasks.",
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
toolCall.args as z.infer<typeof updatePlanTasksSchema>
|
||||
).updated_plan_tasks.map((p) => p);
|
||||
}
|
||||
|
||||
const updatePlanTasks = traceable(updatePlanTasksFunc, {
|
||||
name: "update_plan_tasks",
|
||||
});
|
||||
|
||||
export async function rewritePlan(
|
||||
state: PlannerGraphState,
|
||||
config: GraphConfig,
|
||||
): Promise<PlannerGraphUpdate> {
|
||||
if (!state.planChangeRequest) {
|
||||
throw new Error("No plan change request found.");
|
||||
}
|
||||
|
||||
const model = await loadModel(config, LLMTask.PLANNER);
|
||||
const modelSupportsParallelToolCallsParam = supportsParallelToolCallsParam(
|
||||
config,
|
||||
LLMTask.PLANNER,
|
||||
);
|
||||
const tasksToModify = await identifyTasksToModify(
|
||||
state,
|
||||
model,
|
||||
modelSupportsParallelToolCallsParam,
|
||||
);
|
||||
const updatedPlanTasks = await updatePlanTasks(
|
||||
state,
|
||||
tasksToModify,
|
||||
model,
|
||||
modelSupportsParallelToolCallsParam,
|
||||
);
|
||||
|
||||
return {
|
||||
proposedPlan: updatedPlanTasks,
|
||||
};
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ import {
|
|||
HumanMessage,
|
||||
} from "@langchain/core/messages";
|
||||
import { BindToolsInput } from "@langchain/core/language_models/chat_models";
|
||||
import { shouldCreateIssue } from "../../../../utils/should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "GenerateMessageNode");
|
||||
|
||||
|
|
@ -280,10 +281,14 @@ export async function generateAction(
|
|||
const markTaskCompletedTool = createMarkTaskCompletedToolFields();
|
||||
const isAnthropicModel = modelName.includes("claude-");
|
||||
|
||||
const [missingMessages, { taskPlan: latestTaskPlan }] = await Promise.all([
|
||||
getMissingMessages(state, config),
|
||||
getPlansFromIssue(state, config),
|
||||
]);
|
||||
const [missingMessages, { taskPlan: latestTaskPlan }] = shouldCreateIssue(
|
||||
config,
|
||||
)
|
||||
? await Promise.all([
|
||||
getMissingMessages(state, config),
|
||||
getPlansFromIssue(state, config),
|
||||
])
|
||||
: [[], { taskPlan: null }];
|
||||
|
||||
const { providerTools, providerMessages } = await createToolsAndPrompt(
|
||||
state,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
MAX_INTERNAL_TOKENS,
|
||||
} from "../../../utils/tokens.js";
|
||||
import { z } from "zod";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "HandleCompletedTask");
|
||||
|
||||
|
|
@ -88,7 +89,7 @@ export async function handleCompletedTask(
|
|||
summary,
|
||||
);
|
||||
// Update the github issue to reflect this task as completed.
|
||||
if (!isLocalMode(config)) {
|
||||
if (!isLocalMode(config) && shouldCreateIssue(config)) {
|
||||
await addTaskPlanToIssue(
|
||||
{
|
||||
githubIssueId: state.githubIssueId,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import {
|
|||
} from "../../../utils/github/types.js";
|
||||
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
||||
import { GITHUB_USER_LOGIN_HEADER } from "@open-swe/shared/constants";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "Open PR");
|
||||
|
||||
|
|
@ -191,6 +192,9 @@ export async function openPullRequest(
|
|||
| GitHubPullRequestList[number]
|
||||
| GitHubPullRequestUpdate
|
||||
| null = null;
|
||||
|
||||
const prBody = `${shouldCreateIssue(config) ? `Fixes #${state.githubIssueId}` : ""}${userLogin ? `\n\nOwner: @${userLogin}` : ""}\n\n${body}`;
|
||||
|
||||
if (!prForTask) {
|
||||
// No PR created yet. Shouldn't be possible, but we have a condition here anyway
|
||||
pullRequest = await createPullRequest({
|
||||
|
|
@ -198,7 +202,7 @@ export async function openPullRequest(
|
|||
repo,
|
||||
headBranch: branchName,
|
||||
title,
|
||||
body: `Fixes #${state.githubIssueId}${userLogin ? `\n\nOwner: @${userLogin}` : ""}\n\n${body}`,
|
||||
body: prBody,
|
||||
githubInstallationToken,
|
||||
baseBranch: state.targetRepository.branch,
|
||||
});
|
||||
|
|
@ -208,7 +212,7 @@ export async function openPullRequest(
|
|||
owner,
|
||||
repo,
|
||||
title,
|
||||
body: `Fixes #${state.githubIssueId}${userLogin ? `\n\nOwner: @${userLogin}` : ""}\n\n${body}`,
|
||||
body: prBody,
|
||||
pullNumber: prForTask,
|
||||
githubInstallationToken,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import {
|
|||
REQUEST_HELP_NODE_ID,
|
||||
} from "@open-swe/shared/open-swe/custom-node-events";
|
||||
import { postGitHubIssueComment } from "../../../utils/github/plan.js";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
import { isLocalMode } from "@open-swe/shared/open-swe/local-mode";
|
||||
|
||||
const constructDescription = (helpRequest: string): string => {
|
||||
return `The agent has requested help. Here is the help request:
|
||||
|
|
@ -60,12 +62,13 @@ export async function requestHelp(
|
|||
throw new Error("Thread ID not found in config");
|
||||
}
|
||||
|
||||
const userLogin = config.configurable?.[GITHUB_USER_LOGIN_HEADER];
|
||||
const userTag = userLogin ? `@${userLogin} ` : "";
|
||||
if (!isLocalMode(config) && shouldCreateIssue(config)) {
|
||||
const userLogin = config.configurable?.[GITHUB_USER_LOGIN_HEADER];
|
||||
const userTag = userLogin ? `@${userLogin} ` : "";
|
||||
const runUrl = getOpenSweAppUrl(threadId);
|
||||
|
||||
const runUrl = getOpenSweAppUrl(threadId);
|
||||
const commentBody = runUrl
|
||||
? `### 🤖 Open SWE Needs Help
|
||||
const commentBody = runUrl
|
||||
? `### 🤖 Open SWE Needs Help
|
||||
|
||||
${userTag}I've encountered a situation where I need human assistance to continue.
|
||||
|
||||
|
|
@ -75,7 +78,7 @@ ${toolCall.args.help_request}
|
|||
You can view and respond to this request in the [Open SWE interface](${runUrl}).
|
||||
|
||||
Please provide guidance so I can continue working on this issue.`
|
||||
: `### 🤖 Open SWE Needs Help
|
||||
: `### 🤖 Open SWE Needs Help
|
||||
|
||||
${userTag}I've encountered a situation where I need human assistance to continue.
|
||||
|
||||
|
|
@ -84,12 +87,13 @@ ${toolCall.args.help_request}
|
|||
|
||||
Please check the Open SWE interface to respond to this request.`;
|
||||
|
||||
await postGitHubIssueComment({
|
||||
githubIssueId: state.githubIssueId,
|
||||
targetRepository: state.targetRepository,
|
||||
commentBody,
|
||||
config,
|
||||
});
|
||||
await postGitHubIssueComment({
|
||||
githubIssueId: state.githubIssueId,
|
||||
targetRepository: state.targetRepository,
|
||||
commentBody,
|
||||
config,
|
||||
});
|
||||
}
|
||||
|
||||
const interruptInput: HumanInterrupt = {
|
||||
action_request: {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import { formatCustomRulesPrompt } from "../../../utils/custom-rules.js";
|
|||
import { trackCachePerformance } from "../../../utils/caching.js";
|
||||
import { getModelManager } from "../../../utils/llms/model-manager.js";
|
||||
import { addTaskPlanToIssue } from "../../../utils/github/issue-task.js";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
import { isLocalMode } from "@open-swe/shared/open-swe/local-mode";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "UpdatePlanNode");
|
||||
|
||||
|
|
@ -217,15 +219,17 @@ export async function updatePlan(
|
|||
newPlanItems,
|
||||
"agent",
|
||||
);
|
||||
// Update the github issue to reflect the changes in the plan
|
||||
await addTaskPlanToIssue(
|
||||
{
|
||||
githubIssueId: state.githubIssueId,
|
||||
targetRepository: state.targetRepository,
|
||||
},
|
||||
config,
|
||||
newTaskPlan,
|
||||
);
|
||||
if (!isLocalMode(config) && shouldCreateIssue(config)) {
|
||||
// Update the github issue to reflect the changes in the plan
|
||||
await addTaskPlanToIssue(
|
||||
{
|
||||
githubIssueId: state.githubIssueId,
|
||||
targetRepository: state.targetRepository,
|
||||
},
|
||||
config,
|
||||
newTaskPlan,
|
||||
);
|
||||
}
|
||||
|
||||
const toolMessage = new ToolMessage({
|
||||
id: uuidv4(),
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import {
|
|||
import { trackCachePerformance } from "../../../utils/caching.js";
|
||||
import { getModelManager } from "../../../utils/llms/model-manager.js";
|
||||
import { createScratchpadTool } from "../../../tools/scratchpad.js";
|
||||
import { shouldCreateIssue } from "../../../utils/should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "FinalReview");
|
||||
|
||||
|
|
@ -192,7 +193,7 @@ export async function finalReview(
|
|||
"agent",
|
||||
);
|
||||
|
||||
if (!isLocalMode(config)) {
|
||||
if (!isLocalMode(config) && shouldCreateIssue(config)) {
|
||||
await addTaskPlanToIssue(
|
||||
{
|
||||
githubIssueId: state.githubIssueId,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { DEFAULT_EXCLUDED_PATTERNS } from "./constants.js";
|
|||
import { escapeRegExp } from "../string-utils.js";
|
||||
import { isLocalMode } from "@open-swe/shared/open-swe/local-mode";
|
||||
import { createShellExecutor } from "../shell-executor/index.js";
|
||||
import { shouldCreateIssue } from "../should-create-issue.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "GitHub-Git");
|
||||
|
||||
|
|
@ -336,33 +337,39 @@ export async function checkoutBranchAndCommit(
|
|||
let updatedTaskPlan: TaskPlan | undefined;
|
||||
const activeTask = getActiveTask(options.taskPlan);
|
||||
const prForTask = getPullRequestNumberFromActiveTask(options.taskPlan);
|
||||
|
||||
if (!prForTask) {
|
||||
logger.info("First commit detected, creating a draft pull request.");
|
||||
const hasIssue = shouldCreateIssue(config);
|
||||
|
||||
const pullRequest = await createPullRequest({
|
||||
owner: targetRepository.owner,
|
||||
repo: targetRepository.repo,
|
||||
headBranch: branchName,
|
||||
title: `[WIP]: ${activeTask?.title ?? "Open SWE task"}`,
|
||||
body: `**WORK IN PROGRESS OPEN SWE PR**\n\nFixes: #${options.githubIssueId}`,
|
||||
body: `**WORK IN PROGRESS OPEN SWE PR**${hasIssue ? `\n\nFixes: #${options.githubIssueId}` : ""}`,
|
||||
githubInstallationToken: options.githubInstallationToken,
|
||||
draft: true,
|
||||
baseBranch: targetRepository.branch,
|
||||
nullOnError: true,
|
||||
});
|
||||
|
||||
if (pullRequest) {
|
||||
updatedTaskPlan = addPullRequestNumberToActiveTask(
|
||||
options.taskPlan,
|
||||
pullRequest.number,
|
||||
);
|
||||
await addTaskPlanToIssue(
|
||||
{
|
||||
githubIssueId: options.githubIssueId,
|
||||
targetRepository,
|
||||
},
|
||||
config,
|
||||
updatedTaskPlan,
|
||||
);
|
||||
logger.info(`Draft pull request created: #${pullRequest.number}`);
|
||||
if (hasIssue) {
|
||||
await addTaskPlanToIssue(
|
||||
{
|
||||
githubIssueId: options.githubIssueId,
|
||||
targetRepository,
|
||||
},
|
||||
config,
|
||||
updatedTaskPlan,
|
||||
);
|
||||
logger.info(`Draft pull request created: #${pullRequest.number}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
5
apps/open-swe/src/utils/should-create-issue.ts
Normal file
5
apps/open-swe/src/utils/should-create-issue.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { GraphConfig } from "@open-swe/shared/open-swe/types";
|
||||
|
||||
export function shouldCreateIssue(config: GraphConfig): boolean {
|
||||
return !!config.configurable?.shouldCreateIssue;
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import { getMessageContentString } from "@open-swe/shared/messages";
|
|||
import { extractContentWithoutDetailsFromIssueBody } from "./github/issue-messages.js";
|
||||
import { isLocalMode } from "@open-swe/shared/open-swe/local-mode";
|
||||
import { GraphConfig } from "@open-swe/shared/open-swe/types";
|
||||
import { shouldCreateIssue } from "./should-create-issue.js";
|
||||
|
||||
// TODO: Might want a better way of doing this.
|
||||
// maybe add a new kwarg `isRequest` and have this return the last human message with that field?
|
||||
|
|
@ -55,7 +56,10 @@ export function getRecentUserRequest(
|
|||
): string | HumanMessage {
|
||||
let recentUserMessage: HumanMessage | undefined;
|
||||
|
||||
if (options?.config && isLocalMode(options.config)) {
|
||||
if (
|
||||
options?.config &&
|
||||
(isLocalMode(options.config) || !shouldCreateIssue(options.config))
|
||||
) {
|
||||
// In local mode, get the last human message regardless of flags
|
||||
recentUserMessage = messages.findLast(isHumanMessage);
|
||||
} else {
|
||||
|
|
|
|||
22
apps/web/src/components/icons/github-issue.tsx
Normal file
22
apps/web/src/components/icons/github-issue.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
interface GitHubIssueSVGProps {
|
||||
width?: string;
|
||||
height?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const GitHubIssueSVG = ({
|
||||
width = "100%",
|
||||
height = "100%",
|
||||
className,
|
||||
}: GitHubIssueSVGProps) => (
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
width={width}
|
||||
height={height}
|
||||
fill="currentColor"
|
||||
className={className}
|
||||
>
|
||||
<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path>
|
||||
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path>
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Archive, ListChecks } from "lucide-react";
|
||||
import { Archive, Eye, EyeOff, List, ListChecks } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { TerminalInput } from "./terminal-input";
|
||||
import { useFileUpload } from "@/hooks/useFileUpload";
|
||||
|
|
@ -32,6 +32,7 @@ import { threadsToMetadata } from "@/lib/thread-utils";
|
|||
import { Settings, BookOpen } from "lucide-react";
|
||||
import NextLink from "next/link";
|
||||
import { OpenSWELogoSVG } from "../icons/openswe";
|
||||
import { DEFAULT_CONFIG_KEY, useConfigStore } from "@/hooks/useConfigStore";
|
||||
|
||||
function OpenSettingsButton() {
|
||||
return (
|
||||
|
|
@ -86,6 +87,8 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
|||
const apiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL ?? "";
|
||||
const [draftToLoad, setDraftToLoad] = useState("");
|
||||
const assistantId: string | undefined = MANAGER_GRAPH_ID;
|
||||
const { getConfig } = useConfigStore();
|
||||
const config = getConfig(DEFAULT_CONFIG_KEY);
|
||||
const {
|
||||
contentBlocks,
|
||||
setContentBlocks,
|
||||
|
|
@ -95,6 +98,9 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
|||
handlePaste,
|
||||
} = useFileUpload();
|
||||
const [autoAccept, setAutoAccept] = useState(false);
|
||||
const [shouldCreateIssue, setShouldCreateIssue] = useState(
|
||||
config?.shouldCreateIssue != null ? !!config.shouldCreateIssue : true,
|
||||
);
|
||||
|
||||
const threadsMetadata = useMemo(() => threadsToMetadata(threads), [threads]);
|
||||
const displayThreads = threadsMetadata.slice(0, 4);
|
||||
|
|
@ -172,11 +178,13 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
|||
draftToLoad={draftToLoad}
|
||||
autoAcceptPlan={autoAccept}
|
||||
setAutoAcceptPlan={setAutoAccept}
|
||||
shouldCreateIssue={shouldCreateIssue}
|
||||
setShouldCreateIssue={setShouldCreateIssue}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<TooltipIconButton
|
||||
variant={autoAccept ? "default" : "ghost"}
|
||||
tooltip="Automatically accept the plan"
|
||||
tooltip="Whether or not to automatically accept the plan"
|
||||
className={cn(
|
||||
"transition-colors duration-200",
|
||||
autoAccept
|
||||
|
|
@ -186,7 +194,29 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
|||
onClick={() => setAutoAccept((prev) => !prev)}
|
||||
side="bottom"
|
||||
>
|
||||
<ListChecks className="size-4" />
|
||||
{autoAccept ? (
|
||||
<ListChecks className="size-4" />
|
||||
) : (
|
||||
<List className="size-4" />
|
||||
)}
|
||||
</TooltipIconButton>
|
||||
<TooltipIconButton
|
||||
variant={shouldCreateIssue ? "ghost" : "default"}
|
||||
tooltip="Whether or not to create a GitHub issue for the request"
|
||||
className={cn(
|
||||
"transition-colors duration-200",
|
||||
shouldCreateIssue
|
||||
? "text-muted-foreground hover:text-foreground"
|
||||
: "bg-primary hover:bg-primary/90",
|
||||
)}
|
||||
onClick={() => setShouldCreateIssue((prev) => !prev)}
|
||||
side="bottom"
|
||||
>
|
||||
{shouldCreateIssue ? (
|
||||
<Eye className="size-4" />
|
||||
) : (
|
||||
<EyeOff className="size-4" />
|
||||
)}
|
||||
</TooltipIconButton>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ interface ManagerChatProps {
|
|||
name: string | null;
|
||||
email: string | null;
|
||||
};
|
||||
disableSubmit?: boolean;
|
||||
}
|
||||
|
||||
function extractResponseFromMessage(message: Message): string {
|
||||
|
|
@ -162,6 +163,7 @@ export function ManagerChat({
|
|||
cancelRun,
|
||||
errorState,
|
||||
githubUser,
|
||||
disableSubmit,
|
||||
}: ManagerChatProps) {
|
||||
return (
|
||||
<div className="border-border bg-muted/30 flex h-full w-1/3 flex-col overflow-hidden border-r">
|
||||
|
|
@ -252,7 +254,13 @@ export function ManagerChat({
|
|||
placeholder="Type your message..."
|
||||
className="border-border bg-background text-foreground placeholder:text-muted-foreground min-h-[60px] flex-1 resize-none text-sm"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !isLoading) {
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
(e.metaKey || e.ctrlKey) &&
|
||||
!isLoading &&
|
||||
!disableSubmit &&
|
||||
!!chatInput.trim()
|
||||
) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
|
|
@ -270,7 +278,7 @@ export function ManagerChat({
|
|||
) : (
|
||||
<Button
|
||||
onClick={handleSendMessage}
|
||||
disabled={!chatInput.trim()}
|
||||
disabled={!chatInput.trim() || disableSubmit}
|
||||
size="icon"
|
||||
variant="brand"
|
||||
className="size-8 rounded-full border border-white/20 transition-all duration-200 hover:border-white/30 disabled:border-transparent"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ interface TerminalInputProps {
|
|||
setQuickActionPrompt?: Dispatch<SetStateAction<string>>;
|
||||
autoAcceptPlan: boolean;
|
||||
setAutoAcceptPlan: Dispatch<SetStateAction<boolean>>;
|
||||
shouldCreateIssue: boolean;
|
||||
setShouldCreateIssue: Dispatch<SetStateAction<boolean>>;
|
||||
draftToLoad?: string;
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +73,8 @@ export function TerminalInput({
|
|||
setQuickActionPrompt,
|
||||
autoAcceptPlan,
|
||||
setAutoAcceptPlan,
|
||||
shouldCreateIssue,
|
||||
setShouldCreateIssue,
|
||||
draftToLoad,
|
||||
}: TerminalInputProps) {
|
||||
const { push } = useRouter();
|
||||
|
|
@ -164,6 +168,7 @@ export function TerminalInput({
|
|||
recursion_limit: 400,
|
||||
configurable: {
|
||||
...defaultConfig,
|
||||
shouldCreateIssue,
|
||||
[GITHUB_USER_LOGIN_HEADER]: user.login,
|
||||
},
|
||||
},
|
||||
|
|
@ -199,6 +204,11 @@ export function TerminalInput({
|
|||
setMessage("");
|
||||
setContentBlocks([]);
|
||||
setAutoAcceptPlan(false);
|
||||
setShouldCreateIssue(
|
||||
defaultConfig?.shouldCreateIssue != null
|
||||
? !!defaultConfig.shouldCreateIssue
|
||||
: true,
|
||||
);
|
||||
} catch (e) {
|
||||
if (
|
||||
typeof e === "object" &&
|
||||
|
|
|
|||
|
|
@ -114,6 +114,14 @@ export function ThreadView({
|
|||
});
|
||||
|
||||
const [errorState, setErrorState] = useState<ErrorState | null>(null);
|
||||
const [hasGitHubIssue, setHasGitHubIssue] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
stream.client.threads.get(displayThread.id).then((thread) => {
|
||||
const configurable = (thread as Record<string, any>).config?.configurable;
|
||||
setHasGitHubIssue(!!configurable?.shouldCreateIssue);
|
||||
});
|
||||
}, [displayThread.id]);
|
||||
|
||||
// Load optimistic message from sessionStorage
|
||||
useEffect(() => {
|
||||
|
|
@ -348,6 +356,10 @@ export function ThreadView({
|
|||
]
|
||||
: filteredMessages;
|
||||
|
||||
const shouldDisableManagerInput = !hasGitHubIssue
|
||||
? stream.isLoading || plannerStream.isLoading || programmerStream.isLoading
|
||||
: false;
|
||||
|
||||
return (
|
||||
<div className="bg-background flex h-screen flex-1 flex-col">
|
||||
{/* Header */}
|
||||
|
|
@ -397,6 +409,7 @@ export function ThreadView({
|
|||
cancelRun={cancelRun}
|
||||
errorState={errorState}
|
||||
githubUser={user || undefined}
|
||||
disableSubmit={shouldDisableManagerInput}
|
||||
/>
|
||||
{/* Right Side - Actions & Plan */}
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -449,6 +449,14 @@ export const GraphConfigurationMetadata: {
|
|||
"JSON configuration for custom MCP servers. LangGraph docs server is set by default. See the `mcpServers` field of the LangChain MCP Adapters `ClientConfig` type for information on this schema. [Documentation here](https://v03.api.js.langchain.com/types/_langchain_mcp_adapters.ClientConfig.html).",
|
||||
},
|
||||
},
|
||||
shouldCreateIssue: {
|
||||
x_open_swe_ui_config: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
description:
|
||||
"Whether or not to create GitHub issues for all requests. Can be overridden on a per-request basis via the 'eye' icon in the chat input area.",
|
||||
},
|
||||
},
|
||||
apiKeys: {
|
||||
x_open_swe_ui_config: {
|
||||
type: "hidden",
|
||||
|
|
@ -614,6 +622,13 @@ export const GraphConfiguration = z.object({
|
|||
maxTokens: withLangGraph(z.number().optional(), {
|
||||
metadata: GraphConfigurationMetadata.maxTokens,
|
||||
}),
|
||||
/**
|
||||
* Whether or not to create an issue for this request.
|
||||
* @default true
|
||||
*/
|
||||
shouldCreateIssue: withLangGraph(z.boolean().optional(), {
|
||||
metadata: GraphConfigurationMetadata.shouldCreateIssue,
|
||||
}),
|
||||
/**
|
||||
* User defined API keys to use
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue