Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions gui/src/pages/config/components/ToolPolicyItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { useFontSize } from "../../../components/ui/font";
import { useAppSelector } from "../../../redux/hooks";
import { addTool, setToolPolicy } from "../../../redux/slices/uiSlice";
import { isEditTool } from "../../../util/toolCallState";

interface ToolPolicyItemProps {
tool: Tool;
Expand All @@ -28,12 +29,22 @@ interface ToolPolicyItemProps {

export function ToolPolicyItem(props: ToolPolicyItemProps) {
const dispatch = useDispatch();
const policy = useAppSelector(
const toolPolicy = useAppSelector(
(state) => state.ui.toolSettings[props.tool.function.name],
);
const [isExpanded, setIsExpanded] = useState(false);
const mode = useAppSelector((state) => state.session.mode);

const autoAcceptEditToolDiffs = useAppSelector(
(state) => state.config.config.ui?.autoAcceptEditToolDiffs,
);
const isAutoAcceptedToolCall =
isEditTool(props.tool.function.name) && autoAcceptEditToolDiffs;

const policy = isAutoAcceptedToolCall
? "allowedWithoutPermission"
: toolPolicy;

useEffect(() => {
if (!policy) {
dispatch(addTool(props.tool));
Expand All @@ -53,6 +64,7 @@ export function ToolPolicyItem(props: ToolPolicyItemProps) {
const fontSize = useFontSize(-2);

const disabled =
isAutoAcceptedToolCall ||
!props.isGroupEnabled ||
(mode === "plan" &&
props.tool.group === BUILT_IN_GROUP_NAME &&
Expand Down Expand Up @@ -100,6 +112,19 @@ export function ToolPolicyItem(props: ToolPolicyItemProps) {
<InformationCircleIcon className="h-3 w-3 flex-shrink-0 cursor-help text-yellow-500" />
</ToolTip>
) : null}
{isAutoAcceptedToolCall ? (
<ToolTip
place="bottom"
className="flex flex-wrap items-center"
content={
<p className="m-0 p-0">
Auto-Accept Agent Edits setting is on
</p>
}
>
<InformationCircleIcon className="h-3 w-3 flex-shrink-0 cursor-help text-yellow-500" />
</ToolTip>
) : null}
{props.tool.faviconUrl && (
<img
src={props.tool.faviconUrl}
Expand Down Expand Up @@ -139,11 +164,13 @@ export function ToolPolicyItem(props: ToolPolicyItemProps) {
data-tooltip-id={disabled ? disabledTooltipId : undefined}
>
<span className="text-xs">
{disabled || policy === "disabled"
? "Excluded"
: policy === "allowedWithoutPermission"
? "Automatic"
: "Ask First"}
{isAutoAcceptedToolCall
? "Automatic"
: disabled || policy === "disabled"
? "Excluded"
: policy === "allowedWithoutPermission"
? "Automatic"
: "Ask First"}
</span>
<ChevronDownIcon className="h-3 w-3" />
</ListboxButton>
Expand Down
12 changes: 2 additions & 10 deletions gui/src/redux/slices/sessionSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
ToolCallState,
} from "core";
import type { RemoteSessionMetadata } from "core/control-plane/client";
import { BuiltInToolNames } from "core/tools/builtIn";
import { NEW_SESSION_TITLE } from "core/util/constants";
import {
renderChatMessage,
Expand All @@ -36,7 +35,7 @@ import { findLastIndex } from "lodash";
import { v4 as uuidv4 } from "uuid";
import { type InlineErrorMessageType } from "../../components/mainInput/InlineErrorMessage";
import { toolCallCtxItemToCtxItemWithId } from "../../pages/gui/ToolCallDiv/utils";
import { addToolCallDeltaToState } from "../../util/toolCallState";
import { addToolCallDeltaToState, isEditTool } from "../../util/toolCallState";
import { RootState } from "../store";
import { streamResponseThunk } from "../thunks/streamResponse";
import { findChatHistoryItemByToolCallId, findToolCallById } from "../util";
Expand All @@ -51,17 +50,10 @@ import { findChatHistoryItemByToolCallId, findToolCallById } from "../util";
function filterMultipleEditToolCalls(
toolCalls: ToolCallDelta[],
): ToolCallDelta[] {
const editToolNames = [
BuiltInToolNames.EditExistingFile,
BuiltInToolNames.SingleFindAndReplace,
BuiltInToolNames.MultiEdit,
];
let hasSeenEditTool = false;

return toolCalls.filter((toolCall) => {
const isEditTool = editToolNames.includes(toolCall.function?.name as any);

if (isEditTool) {
if (toolCall.function?.name && isEditTool(toolCall.function?.name)) {
if (hasSeenEditTool) {
return false; // Skip this duplicate edit tool
}
Expand Down
12 changes: 12 additions & 0 deletions gui/src/redux/thunks/evaluateToolPolicies.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ToolPolicy } from "@continuedev/terminal-security";
import { Tool, ToolCallState } from "core";
import { IIdeMessenger } from "../../context/IdeMessenger";
import { isEditTool } from "../../util/toolCallState";
import { errorToolCall, updateToolCallOutput } from "../slices/sessionSlice";
import { DEFAULT_TOOL_SETTING, ToolPolicies } from "../slices/uiSlice";
import { AppThunkDispatch } from "../store";
Expand All @@ -20,7 +21,16 @@ async function evaluateToolPolicy(
activeTools: Tool[],
toolCallState: ToolCallState,
toolPolicies: ToolPolicies,
autoAcceptEditToolDiffs: boolean | undefined,
): Promise<EvaluatedPolicy> {
// allow edit tool calls without permission if auto-accept is enabled
if (
isEditTool(toolCallState.toolCall.function.name) &&
autoAcceptEditToolDiffs
) {
return { policy: "allowedWithoutPermission", toolCallState };
}

const basePolicy =
toolPolicies[toolCallState.toolCall.function.name] ??
activeTools.find(
Expand Down Expand Up @@ -73,6 +83,7 @@ export async function evaluateToolPolicies(
activeTools: Tool[],
generatedToolCalls: ToolCallState[],
toolPolicies: ToolPolicies,
autoAcceptEditToolDiffs: boolean | undefined,
): Promise<EvaluatedPolicy[]> {
// Check if ALL tool calls are auto-approved using dynamic evaluation
const policyResults = await Promise.all(
Expand All @@ -82,6 +93,7 @@ export async function evaluateToolPolicies(
activeTools,
toolCallState,
toolPolicies,
autoAcceptEditToolDiffs,
),
),
);
Expand Down
1 change: 1 addition & 0 deletions gui/src/redux/thunks/streamNormalInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export const streamNormalInput = createAsyncThunk<
activeTools,
generatedCalls3,
toolPolicies,
state3.config.config.ui?.autoAcceptEditToolDiffs,
);
const anyRequireApproval = policies.find(
({ policy }) => policy === "allowedWithPermission",
Expand Down
10 changes: 10 additions & 0 deletions gui/src/util/toolCallState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ToolCallDelta, ToolCallState } from "core";
import { BuiltInToolNames } from "core/tools/builtIn";
import { incrementalParseJson } from "core/util/incrementalParseJson";

// Merge streamed tool calls
Expand Down Expand Up @@ -67,3 +68,12 @@ export function addToolCallDeltaToState(
parsedArgs,
};
}

const editToolNames: string[] = [
BuiltInToolNames.EditExistingFile,
BuiltInToolNames.SingleFindAndReplace,
BuiltInToolNames.MultiEdit,
];
export function isEditTool(toolName: string) {
return editToolNames.includes(toolName);
}
Loading