refactor: unify interrupt handling in Dropbox and Google Drive tools

Refactored the create and delete file functionalities in Dropbox and Google Drive tools to utilize a consistent InterruptResult interface with specific context types. This change enhances code clarity and maintains uniformity in handling user approvals by integrating the useHitlDecision hook for decision dispatching.
This commit is contained in:
Anish Sarkar 2026-04-13 20:21:16 +05:30
parent f844c3288c
commit 71cd04b05e
4 changed files with 50 additions and 145 deletions

View file

@ -16,6 +16,8 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import { useHitlPhase } from "@/hooks/use-hitl-phase";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
import type { InterruptResult, HitlDecision } from "@/lib/hitl";
interface DropboxAccount { interface DropboxAccount {
id: number; id: number;
@ -29,21 +31,11 @@ interface SupportedType {
label: string; label: string;
} }
interface InterruptResult { interface DropboxCreateFileContext {
__interrupt__: true; accounts?: DropboxAccount[];
__decided__?: "approve" | "reject" | "edit"; parent_folders?: Record<number, Array<{ folder_path: string; name: string }>>;
__completed__?: boolean; supported_types?: SupportedType[];
action_requests: Array<{ name: string; args: Record<string, unknown> }>; error?: string;
review_configs: Array<{
action_name: string;
allowed_decisions: Array<"approve" | "edit" | "reject">;
}>;
context?: {
accounts?: DropboxAccount[];
parent_folders?: Record<number, Array<{ folder_path: string; name: string }>>;
supported_types?: SupportedType[];
error?: string;
};
} }
interface SuccessResult { interface SuccessResult {
@ -65,16 +57,7 @@ interface AuthErrorResult {
connector_type?: string; connector_type?: string;
} }
type CreateDropboxFileResult = InterruptResult | SuccessResult | ErrorResult | AuthErrorResult; type CreateDropboxFileResult = InterruptResult<DropboxCreateFileContext> | SuccessResult | ErrorResult | AuthErrorResult;
function isInterruptResult(result: unknown): result is InterruptResult {
return (
typeof result === "object" &&
result !== null &&
"__interrupt__" in result &&
(result as InterruptResult).__interrupt__ === true
);
}
function isErrorResult(result: unknown): result is ErrorResult { function isErrorResult(result: unknown): result is ErrorResult {
return ( return (
@ -100,12 +83,8 @@ function ApprovalCard({
onDecision, onDecision,
}: { }: {
args: { name: string; file_type?: string; content?: string }; args: { name: string; file_type?: string; content?: string };
interruptData: InterruptResult; interruptData: InterruptResult<DropboxCreateFileContext>;
onDecision: (decision: { onDecision: (decision: HitlDecision) => void;
type: "approve" | "reject" | "edit";
message?: string;
edited_action?: { name: string; args: Record<string, unknown> };
}) => void;
}) { }) {
const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const { phase, setProcessing, setRejected } = useHitlPhase(interruptData);
const [isPanelOpen, setIsPanelOpen] = useState(false); const [isPanelOpen, setIsPanelOpen] = useState(false);
@ -455,17 +434,14 @@ export const CreateDropboxFileToolUI = ({
{ name: string; file_type?: string; content?: string }, { name: string; file_type?: string; content?: string },
CreateDropboxFileResult CreateDropboxFileResult
>) => { >) => {
const { dispatch } = useHitlDecision();
if (!result) return null; if (!result) return null;
if (isInterruptResult(result)) { if (isInterruptResult(result)) {
return ( return (
<ApprovalCard <ApprovalCard
args={args} args={args}
interruptData={result} interruptData={result as InterruptResult<DropboxCreateFileContext>}
onDecision={(decision) => { onDecision={(decision) => dispatch([decision])}
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/> />
); );
} }

View file

@ -7,6 +7,8 @@ import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import { useHitlPhase } from "@/hooks/use-hitl-phase";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
import type { InterruptResult, HitlDecision } from "@/lib/hitl";
interface DropboxAccount { interface DropboxAccount {
id: number; id: number;
@ -22,13 +24,10 @@ interface DropboxFile {
document_id?: number; document_id?: number;
} }
interface InterruptResult { interface DropboxTrashFileContext {
__interrupt__: true; account?: DropboxAccount;
__decided__?: "approve" | "reject"; file?: DropboxFile;
__completed__?: boolean; error?: string;
action_requests: Array<{ name: string; args: Record<string, unknown> }>;
review_configs: Array<{ action_name: string; allowed_decisions: Array<"approve" | "reject"> }>;
context?: { account?: DropboxAccount; file?: DropboxFile; error?: string };
} }
interface SuccessResult { interface SuccessResult {
@ -52,20 +51,12 @@ interface AuthErrorResult {
} }
type DeleteDropboxFileResult = type DeleteDropboxFileResult =
| InterruptResult | InterruptResult<DropboxTrashFileContext>
| SuccessResult | SuccessResult
| ErrorResult | ErrorResult
| NotFoundResult | NotFoundResult
| AuthErrorResult; | AuthErrorResult;
function isInterruptResult(result: unknown): result is InterruptResult {
return (
typeof result === "object" &&
result !== null &&
"__interrupt__" in result &&
(result as InterruptResult).__interrupt__ === true
);
}
function isErrorResult(result: unknown): result is ErrorResult { function isErrorResult(result: unknown): result is ErrorResult {
return ( return (
typeof result === "object" && typeof result === "object" &&
@ -95,12 +86,8 @@ function ApprovalCard({
interruptData, interruptData,
onDecision, onDecision,
}: { }: {
interruptData: InterruptResult; interruptData: InterruptResult<DropboxTrashFileContext>;
onDecision: (decision: { onDecision: (decision: HitlDecision) => void;
type: "approve" | "reject";
message?: string;
edited_action?: { name: string; args: Record<string, unknown> };
}) => void;
}) { }) {
const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const { phase, setProcessing, setRejected } = useHitlPhase(interruptData);
const [deleteFromKb, setDeleteFromKb] = useState(false); const [deleteFromKb, setDeleteFromKb] = useState(false);
@ -308,16 +295,13 @@ export const DeleteDropboxFileToolUI = ({
{ file_name: string; delete_from_kb?: boolean }, { file_name: string; delete_from_kb?: boolean },
DeleteDropboxFileResult DeleteDropboxFileResult
>) => { >) => {
const { dispatch } = useHitlDecision();
if (!result) return null; if (!result) return null;
if (isInterruptResult(result)) { if (isInterruptResult(result)) {
return ( return (
<ApprovalCard <ApprovalCard
interruptData={result} interruptData={result as InterruptResult<DropboxTrashFileContext>}
onDecision={(decision) => { onDecision={(decision) => dispatch([decision])}
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/> />
); );
} }

View file

@ -16,6 +16,8 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import { useHitlPhase } from "@/hooks/use-hitl-phase";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
import type { InterruptResult, HitlDecision } from "@/lib/hitl";
interface GoogleDriveAccount { interface GoogleDriveAccount {
id: number; id: number;
@ -23,24 +25,11 @@ interface GoogleDriveAccount {
auth_expired?: boolean; auth_expired?: boolean;
} }
interface InterruptResult { interface DriveCreateFileContext {
__interrupt__: true; accounts?: GoogleDriveAccount[];
__decided__?: "approve" | "reject" | "edit"; supported_types?: string[];
__completed__?: boolean; parent_folders?: Record<number, Array<{ folder_id: string; name: string }>>;
action_requests: Array<{ error?: string;
name: string;
args: Record<string, unknown>;
}>;
review_configs: Array<{
action_name: string;
allowed_decisions: Array<"approve" | "edit" | "reject">;
}>;
context?: {
accounts?: GoogleDriveAccount[];
supported_types?: string[];
parent_folders?: Record<number, Array<{ folder_id: string; name: string }>>;
error?: string;
};
} }
interface SuccessResult { interface SuccessResult {
@ -69,21 +58,12 @@ interface AuthErrorResult {
} }
type CreateGoogleDriveFileResult = type CreateGoogleDriveFileResult =
| InterruptResult | InterruptResult<DriveCreateFileContext>
| SuccessResult | SuccessResult
| ErrorResult | ErrorResult
| InsufficientPermissionsResult | InsufficientPermissionsResult
| AuthErrorResult; | AuthErrorResult;
function isInterruptResult(result: unknown): result is InterruptResult {
return (
typeof result === "object" &&
result !== null &&
"__interrupt__" in result &&
(result as InterruptResult).__interrupt__ === true
);
}
function isErrorResult(result: unknown): result is ErrorResult { function isErrorResult(result: unknown): result is ErrorResult {
return ( return (
typeof result === "object" && typeof result === "object" &&
@ -122,12 +102,8 @@ function ApprovalCard({
onDecision, onDecision,
}: { }: {
args: { name: string; file_type: string; content?: string }; args: { name: string; file_type: string; content?: string };
interruptData: InterruptResult; interruptData: InterruptResult<DriveCreateFileContext>;
onDecision: (decision: { onDecision: (decision: HitlDecision) => void;
type: "approve" | "reject" | "edit";
message?: string;
edited_action?: { name: string; args: Record<string, unknown> };
}) => void;
}) { }) {
const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const { phase, setProcessing, setRejected } = useHitlPhase(interruptData);
const [isPanelOpen, setIsPanelOpen] = useState(false); const [isPanelOpen, setIsPanelOpen] = useState(false);
@ -499,18 +475,15 @@ export const CreateGoogleDriveFileToolUI = ({
{ name: string; file_type: string; content?: string }, { name: string; file_type: string; content?: string },
CreateGoogleDriveFileResult CreateGoogleDriveFileResult
>) => { >) => {
const { dispatch } = useHitlDecision();
if (!result) return null; if (!result) return null;
if (isInterruptResult(result)) { if (isInterruptResult(result)) {
return ( return (
<ApprovalCard <ApprovalCard
args={args} args={args}
interruptData={result} interruptData={result as InterruptResult<DriveCreateFileContext>}
onDecision={(decision) => { onDecision={(decision) => dispatch([decision])}
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/> />
); );
} }

View file

@ -7,6 +7,8 @@ import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import { useHitlPhase } from "@/hooks/use-hitl-phase";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
import type { InterruptResult, HitlDecision } from "@/lib/hitl";
interface GoogleDriveAccount { interface GoogleDriveAccount {
id: number; id: number;
@ -21,23 +23,10 @@ interface GoogleDriveFile {
web_view_link: string; web_view_link: string;
} }
interface InterruptResult { interface DriveTrashFileContext {
__interrupt__: true; account?: GoogleDriveAccount;
__decided__?: "approve" | "reject"; file?: GoogleDriveFile;
__completed__?: boolean; error?: string;
action_requests: Array<{
name: string;
args: Record<string, unknown>;
}>;
review_configs: Array<{
action_name: string;
allowed_decisions: Array<"approve" | "reject">;
}>;
context?: {
account?: GoogleDriveAccount;
file?: GoogleDriveFile;
error?: string;
};
} }
interface SuccessResult { interface SuccessResult {
@ -77,7 +66,7 @@ interface AuthErrorResult {
} }
type DeleteGoogleDriveFileResult = type DeleteGoogleDriveFileResult =
| InterruptResult | InterruptResult<DriveTrashFileContext>
| SuccessResult | SuccessResult
| WarningResult | WarningResult
| ErrorResult | ErrorResult
@ -85,15 +74,6 @@ type DeleteGoogleDriveFileResult =
| InsufficientPermissionsResult | InsufficientPermissionsResult
| AuthErrorResult; | AuthErrorResult;
function isInterruptResult(result: unknown): result is InterruptResult {
return (
typeof result === "object" &&
result !== null &&
"__interrupt__" in result &&
(result as InterruptResult).__interrupt__ === true
);
}
function isErrorResult(result: unknown): result is ErrorResult { function isErrorResult(result: unknown): result is ErrorResult {
return ( return (
typeof result === "object" && typeof result === "object" &&
@ -151,12 +131,8 @@ function ApprovalCard({
interruptData, interruptData,
onDecision, onDecision,
}: { }: {
interruptData: InterruptResult; interruptData: InterruptResult<DriveTrashFileContext>;
onDecision: (decision: { onDecision: (decision: HitlDecision) => void;
type: "approve" | "reject";
message?: string;
edited_action?: { name: string; args: Record<string, unknown> };
}) => void;
}) { }) {
const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const { phase, setProcessing, setRejected } = useHitlPhase(interruptData);
const [deleteFromKb, setDeleteFromKb] = useState(false); const [deleteFromKb, setDeleteFromKb] = useState(false);
@ -416,18 +392,14 @@ export const DeleteGoogleDriveFileToolUI = ({
{ file_name: string; delete_from_kb?: boolean }, { file_name: string; delete_from_kb?: boolean },
DeleteGoogleDriveFileResult DeleteGoogleDriveFileResult
>) => { >) => {
const { dispatch } = useHitlDecision();
if (!result) return null; if (!result) return null;
if (isInterruptResult(result)) { if (isInterruptResult(result)) {
return ( return (
<ApprovalCard <ApprovalCard
interruptData={result} interruptData={result as InterruptResult<DriveTrashFileContext>}
onDecision={(decision) => { onDecision={(decision) => dispatch([decision])}
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/> />
); );
} }