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
2 changes: 1 addition & 1 deletion gui/src/pages/gui/ToolCallDiv/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function ToolCallDiv({

const shouldShowGroupedUI = toolCallStates.length > 1 && isStreamingComplete;
const activeCalls = toolCallStates.filter(
(call) => call.status !== "canceled",
(call) => call.status !== "canceled" && call.status !== "done",
);

const renderToolCall = (toolCallState: ToolCallState) => {
Expand Down
40 changes: 34 additions & 6 deletions gui/src/redux/thunks/streamNormalInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createAsyncThunk, unwrapResult } from "@reduxjs/toolkit";
import { LLMFullCompletionOptions, ModelDescription } from "core";
import { getRuleId } from "core/llm/rules/getSystemMessageWithRules";
import { ToCoreProtocol } from "core/protocol";
import { BUILT_IN_GROUP_NAME } from "core/tools/builtIn";
import { selectActiveTools } from "../selectors/selectActiveTools";
import { selectSelectedChatModel } from "../slices/configSlice";
import {
Expand Down Expand Up @@ -317,14 +318,43 @@ export const streamNormalInput = createAsyncThunk<
generatedCalls3,
toolPolicies,
);
const anyRequireApproval = policies.find(
const autoApprovedPolicies = policies.filter(
({ policy }) => policy === "allowedWithoutPermission",
);
const needsApprovalPolicies = policies.filter(
({ policy }) => policy === "allowedWithPermission",
);

// 4. Execute remaining tool calls
// Only set inactive if not all tools were auto-approved
// This prevents UI flashing for auto-approved tools
if (originalToolCalls.length === 0 || anyRequireApproval) {
if (originalToolCalls.length === 0) {
dispatch(setInactive());
} else if (needsApprovalPolicies.length > 0) {
const builtInReadonlyAutoApproved = autoApprovedPolicies.filter(
({ toolCallState }) =>
toolCallState.tool?.group === BUILT_IN_GROUP_NAME &&
toolCallState.tool?.readonly,
);

if (builtInReadonlyAutoApproved.length > 0) {
const state4 = getState();
if (streamAborter.signal.aborted || !state4.session.isStreaming) {
return;
}
await Promise.all(
builtInReadonlyAutoApproved.map(async ({ toolCallState }) => {
unwrapResult(
await dispatch(
callToolById({
toolCallId: toolCallState.toolCallId,
isAutoApproved: true,
depth: depth + 1,
}),
),
);
}),
);
}

dispatch(setInactive());
} else {
// auto stream cases increase thunk depth by 1 for debugging
Expand All @@ -334,7 +364,6 @@ export const streamNormalInput = createAsyncThunk<
return;
}
if (generatedCalls4.length > 0) {
// All that didn't fail are auto approved - call them
await Promise.all(
generatedCalls4.map(async ({ toolCallId }) => {
unwrapResult(
Expand All @@ -349,7 +378,6 @@ export const streamNormalInput = createAsyncThunk<
}),
);
} else {
// All failed - stream on
for (const { toolCallId } of originalToolCalls) {
unwrapResult(
await dispatch(
Expand Down
Loading