mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-13 17:52:38 +02:00
chat: drop legacy thinking-steps, tool-fallback, hitl modules, and span-indent helper.
This commit is contained in:
parent
9c5a178468
commit
ba0e1e70a0
14 changed files with 0 additions and 1942 deletions
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* Indent tool-call cards that belong to an open delegating ``task`` episode.
|
||||
*
|
||||
* The backend only stamps ``metadata.spanId`` on tool SSE / persisted parts
|
||||
* while a ``task`` is active (see ``AgentEventRelayState.tool_activity_metadata``),
|
||||
* so its presence is sufficient. The opening ``task`` row itself carries the
|
||||
* same span id but stays flush — it is the header of the delegation.
|
||||
*/
|
||||
|
||||
export function shouldIndentToolCallForDelegationSpan(
|
||||
toolName: string,
|
||||
metadata: Record<string, unknown> | undefined
|
||||
): boolean {
|
||||
if (toolName === "task") return false;
|
||||
const v = metadata?.spanId;
|
||||
return typeof v === "string" && v.trim().length > 0;
|
||||
}
|
||||
|
||||
export const DELEGATION_SPAN_INDENT_CLASS = "pl-3 sm:ml-4";
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from "react";
|
||||
import type { HitlDecision } from "./types";
|
||||
|
||||
export type BundleSubmit = (orderedDecisions: HitlDecision[]) => void;
|
||||
|
||||
export interface HitlBundleAPI {
|
||||
toolCallIds: readonly string[];
|
||||
currentStep: number;
|
||||
stagedCount: number;
|
||||
isInBundle: (toolCallId: string) => boolean;
|
||||
isCurrentStep: (toolCallId: string) => boolean;
|
||||
getStaged: (toolCallId: string) => HitlDecision | undefined;
|
||||
stage: (toolCallId: string, decision: HitlDecision) => void;
|
||||
goToStep: (i: number) => void;
|
||||
next: () => void;
|
||||
prev: () => void;
|
||||
submit: () => void;
|
||||
}
|
||||
|
||||
const HitlBundleContext = createContext<HitlBundleAPI | null>(null);
|
||||
const ToolCallIdContext = createContext<string | null>(null);
|
||||
|
||||
export function useHitlBundle(): HitlBundleAPI | null {
|
||||
return useContext(HitlBundleContext);
|
||||
}
|
||||
|
||||
export function useToolCallIdContext(): string | null {
|
||||
return useContext(ToolCallIdContext);
|
||||
}
|
||||
|
||||
export function ToolCallIdProvider({
|
||||
toolCallId,
|
||||
children,
|
||||
}: {
|
||||
toolCallId: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return <ToolCallIdContext.Provider value={toolCallId}>{children}</ToolCallIdContext.Provider>;
|
||||
}
|
||||
|
||||
interface HitlBundleProviderProps {
|
||||
toolCallIds: readonly string[] | null;
|
||||
onSubmit: BundleSubmit;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates only when ``toolCallIds`` has 2+ entries; single-card interrupts
|
||||
* keep their direct ``window`` dispatch path so N=1 UX is unchanged.
|
||||
*/
|
||||
export function HitlBundleProvider({ toolCallIds, onSubmit, children }: HitlBundleProviderProps) {
|
||||
const active = toolCallIds !== null && toolCallIds.length >= 2;
|
||||
const ids = useMemo(() => (active ? [...toolCallIds] : []), [active, toolCallIds]);
|
||||
const bundleKey = ids.join("|");
|
||||
|
||||
// Derived-state-from-props: reset staging + step when the bundle changes.
|
||||
const [prevBundleKey, setPrevBundleKey] = useState(bundleKey);
|
||||
const [staged, setStaged] = useState<Map<string, HitlDecision>>(() => new Map());
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
if (bundleKey !== prevBundleKey) {
|
||||
setPrevBundleKey(bundleKey);
|
||||
setStaged(new Map());
|
||||
setCurrentStep(0);
|
||||
}
|
||||
|
||||
const isInBundle = useCallback((tcId: string) => ids.includes(tcId), [ids]);
|
||||
const isCurrentStep = useCallback(
|
||||
(tcId: string) => active === true && ids[currentStep] === tcId,
|
||||
[active, ids, currentStep]
|
||||
);
|
||||
const getStaged = useCallback((tcId: string) => staged.get(tcId), [staged]);
|
||||
const stage = useCallback(
|
||||
(tcId: string, decision: HitlDecision) => {
|
||||
if (!active || !ids.includes(tcId)) return;
|
||||
setStaged((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tcId, decision);
|
||||
return next;
|
||||
});
|
||||
// Mirror the staged decision onto the card immediately so prev/next
|
||||
// nav doesn't re-show approve/reject buttons for already-decided cards.
|
||||
// Submit's ``hitl-decision`` event re-applies these (no-op) and runs
|
||||
// the actual resume.
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("hitl-stage", { detail: { toolCallId: tcId, decision } })
|
||||
);
|
||||
const idx = ids.indexOf(tcId);
|
||||
if (idx >= 0 && idx < ids.length - 1) {
|
||||
setCurrentStep(idx + 1);
|
||||
}
|
||||
},
|
||||
[active, ids]
|
||||
);
|
||||
const goToStep = useCallback(
|
||||
(i: number) => {
|
||||
if (i < 0 || i >= ids.length) return;
|
||||
setCurrentStep(i);
|
||||
},
|
||||
[ids.length]
|
||||
);
|
||||
const next = useCallback(() => {
|
||||
setCurrentStep((s) => Math.min(s + 1, Math.max(0, ids.length - 1)));
|
||||
}, [ids.length]);
|
||||
const prev = useCallback(() => {
|
||||
setCurrentStep((s) => Math.max(s - 1, 0));
|
||||
}, []);
|
||||
|
||||
const submit = useCallback(() => {
|
||||
if (!active) return;
|
||||
if (staged.size !== ids.length) return;
|
||||
const ordered: HitlDecision[] = [];
|
||||
for (const tcId of ids) {
|
||||
const d = staged.get(tcId);
|
||||
if (!d) return;
|
||||
ordered.push(d);
|
||||
}
|
||||
onSubmit(ordered);
|
||||
}, [active, ids, staged, onSubmit]);
|
||||
|
||||
const value = useMemo<HitlBundleAPI | null>(() => {
|
||||
if (!active) return null;
|
||||
return {
|
||||
toolCallIds: ids,
|
||||
currentStep,
|
||||
stagedCount: staged.size,
|
||||
isInBundle,
|
||||
isCurrentStep,
|
||||
getStaged,
|
||||
stage,
|
||||
goToStep,
|
||||
next,
|
||||
prev,
|
||||
submit,
|
||||
};
|
||||
}, [
|
||||
active,
|
||||
ids,
|
||||
currentStep,
|
||||
staged,
|
||||
isInBundle,
|
||||
isCurrentStep,
|
||||
getStaged,
|
||||
stage,
|
||||
goToStep,
|
||||
next,
|
||||
prev,
|
||||
submit,
|
||||
]);
|
||||
|
||||
return <HitlBundleContext.Provider value={value}>{children}</HitlBundleContext.Provider>;
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
export {
|
||||
type BundleSubmit,
|
||||
type HitlBundleAPI,
|
||||
HitlBundleProvider,
|
||||
ToolCallIdProvider,
|
||||
useHitlBundle,
|
||||
useToolCallIdContext,
|
||||
} from "./bundle-context";
|
||||
export {
|
||||
type HitlRenderTarget,
|
||||
HitlRenderTargetProvider,
|
||||
useHitlRenderTarget,
|
||||
withHitlInTimeline,
|
||||
} from "./render-target";
|
||||
export type {
|
||||
HitlDecision,
|
||||
InterruptActionRequest,
|
||||
InterruptResult,
|
||||
InterruptReviewConfig,
|
||||
} from "./types";
|
||||
export { isInterruptResult } from "./types";
|
||||
export { useHitlDecision } from "./use-hitl-decision";
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
|
||||
import { createContext, useContext } from "react";
|
||||
import { isInterruptResult } from "./types";
|
||||
|
||||
/**
|
||||
* Where this tool-call card is currently rendering.
|
||||
*
|
||||
* - ``"body"`` (default) — assistant-ui's ``MessagePrimitive.Parts`` renders
|
||||
* the card inside the message bubble.
|
||||
* - ``"timeline"`` — ``ThinkingStepsDisplay`` renders the SAME component
|
||||
* inline under the matching step row so the HITL approval lives in the
|
||||
* chain-of-thought instead of as a standalone card in the message body.
|
||||
*
|
||||
* The two render targets share one component implementation; the context
|
||||
* lets the body render skip itself when the timeline copy will show the
|
||||
* card, avoiding a double-render.
|
||||
*/
|
||||
export type HitlRenderTarget = "body" | "timeline";
|
||||
|
||||
const HitlRenderTargetContext = createContext<HitlRenderTarget>("body");
|
||||
|
||||
export const HitlRenderTargetProvider = HitlRenderTargetContext.Provider;
|
||||
|
||||
export function useHitlRenderTarget(): HitlRenderTarget {
|
||||
return useContext(HitlRenderTargetContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the body render of a tool-call whose result is a HITL interrupt.
|
||||
* The same component is mounted again inside ``ThinkingStepsDisplay``
|
||||
* with ``HitlRenderTargetProvider value="timeline"`` — that copy renders
|
||||
* normally, so the card "moves" from the message body to the timeline.
|
||||
*
|
||||
* Pure pass-through for non-HITL results AND for the timeline render.
|
||||
*/
|
||||
export function withHitlInTimeline(
|
||||
Component: ToolCallMessagePartComponent
|
||||
): ToolCallMessagePartComponent {
|
||||
const Wrapped: ToolCallMessagePartComponent = (props) => {
|
||||
const target = useHitlRenderTarget();
|
||||
if (target === "body" && isInterruptResult(props.result)) return null;
|
||||
return <Component {...props} />;
|
||||
};
|
||||
Wrapped.displayName = `withHitlInTimeline(${Component.displayName ?? Component.name ?? "ToolUI"})`;
|
||||
return Wrapped;
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
* Shared types for Human-in-the-Loop (HITL) approval across all tools.
|
||||
*
|
||||
* Every tool-ui component that handles interrupts should import from here
|
||||
* instead of defining its own `InterruptResult` / `isInterruptResult`.
|
||||
*/
|
||||
|
||||
export interface InterruptActionRequest {
|
||||
name: string;
|
||||
args: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface InterruptReviewConfig {
|
||||
action_name: string;
|
||||
allowed_decisions: Array<"approve" | "edit" | "reject">;
|
||||
}
|
||||
|
||||
export interface InterruptResult<C extends Record<string, unknown> = Record<string, unknown>> {
|
||||
__interrupt__: true;
|
||||
__decided__?: "approve" | "reject" | "edit";
|
||||
__completed__?: boolean;
|
||||
action_requests: InterruptActionRequest[];
|
||||
review_configs: InterruptReviewConfig[];
|
||||
interrupt_type?: string;
|
||||
context?: C;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export function isInterruptResult(result: unknown): result is InterruptResult {
|
||||
return (
|
||||
typeof result === "object" &&
|
||||
result !== null &&
|
||||
"__interrupt__" in result &&
|
||||
(result as InterruptResult).__interrupt__ === true
|
||||
);
|
||||
}
|
||||
|
||||
export interface HitlDecision {
|
||||
type: "approve" | "reject" | "edit";
|
||||
message?: string;
|
||||
edited_action?: {
|
||||
name: string;
|
||||
args: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* Shared hook for dispatching HITL decisions.
|
||||
*
|
||||
* Tool-ui cards always call ``dispatch([decision])``. When a multi-card bundle
|
||||
* is active (``HitlBundleProvider``), the dispatch is intercepted and staged
|
||||
* against this card's ``toolCallId`` so the orchestrator can submit one
|
||||
* ordered N-decision payload. With no bundle active (N=1 path), it falls back
|
||||
* to the legacy ``window`` event the host listens for in ``page.tsx``.
|
||||
*/
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useHitlBundle, useToolCallIdContext } from "./bundle-context";
|
||||
import type { HitlDecision } from "./types";
|
||||
|
||||
export function useHitlDecision() {
|
||||
const bundle = useHitlBundle();
|
||||
const toolCallId = useToolCallIdContext();
|
||||
|
||||
const dispatch = useCallback(
|
||||
(decisions: HitlDecision[]) => {
|
||||
if (bundle && toolCallId && bundle.isInBundle(toolCallId) && decisions.length > 0) {
|
||||
if (decisions.length > 1 && process.env.NODE_ENV !== "production") {
|
||||
// Tool-ui cards stage one decision per call; a multi-decision
|
||||
// dispatch into an active bundle would silently drop tail entries.
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
"[hitl] dispatch received %d decisions inside an active bundle; only [0] will be staged for %s",
|
||||
decisions.length,
|
||||
toolCallId
|
||||
);
|
||||
}
|
||||
bundle.stage(toolCallId, decisions[0]);
|
||||
return;
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent("hitl-decision", { detail: { decisions } }));
|
||||
},
|
||||
[bundle, toolCallId]
|
||||
);
|
||||
|
||||
return { dispatch };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue