171 lines
5.3 KiB
JavaScript
171 lines
5.3 KiB
JavaScript
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
const require_CopilotKitProvider = require('../providers/CopilotKitProvider.cjs');
|
|
const require_use_agent = require('./use-agent.cjs');
|
|
let react = require("react");
|
|
|
|
//#region src/hooks/use-interrupt.tsx
|
|
const INTERRUPT_EVENT_NAME = "on_interrupt";
|
|
function isPromiseLike(value) {
|
|
return (typeof value === "object" || typeof value === "function") && value !== null && typeof Reflect.get(value, "then") === "function";
|
|
}
|
|
/**
|
|
* Handles agent interrupts (`on_interrupt`) with optional filtering, preprocessing, and resume behavior.
|
|
*
|
|
* The hook listens to custom events on the active agent, stores interrupt payloads per run,
|
|
* and surfaces a render callback once the run finalizes. Call `resolve` from your UI to resume
|
|
* execution with user-provided data.
|
|
*
|
|
* - `renderInChat: true` (default): the element is published into `<CopilotChat>` and this hook returns `void`.
|
|
* - `renderInChat: false`: the hook returns the interrupt element so you can place it anywhere in your component tree.
|
|
*
|
|
* `event.value` is typed as `any` since the interrupt payload shape depends on your agent.
|
|
* Type-narrow it in your callbacks (e.g. `handler`, `enabled`, `render`) as needed.
|
|
*
|
|
* @typeParam TResult - Inferred from `handler` return type. Exposed as `result` in `render`.
|
|
* @param config - Interrupt configuration (renderer, optional handler/filter, and render mode).
|
|
* @returns When `renderInChat` is `false`, returns the interrupt element (or `null` when idle).
|
|
* Otherwise returns `void` and publishes the element into chat. In `render`, `result` is always
|
|
* either the handler's resolved return value or `null` (including when no handler is provided,
|
|
* when filtering skips the interrupt, or when handler execution fails).
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* import { useInterrupt } from "@copilotkitnext/react";
|
|
*
|
|
* function InterruptUI() {
|
|
* useInterrupt({
|
|
* render: ({ event, resolve }) => (
|
|
* <div>
|
|
* <p>{event.value.question}</p>
|
|
* <button onClick={() => resolve({ approved: true })}>Approve</button>
|
|
* <button onClick={() => resolve({ approved: false })}>Reject</button>
|
|
* </div>
|
|
* ),
|
|
* });
|
|
*
|
|
* return null;
|
|
* }
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* import { useInterrupt } from "@copilotkitnext/react";
|
|
*
|
|
* function CustomPanel() {
|
|
* const interruptElement = useInterrupt({
|
|
* renderInChat: false,
|
|
* enabled: (event) => event.value.startsWith("approval:"),
|
|
* handler: async ({ event }) => ({ label: event.value.toUpperCase() }),
|
|
* render: ({ event, result, resolve }) => (
|
|
* <aside>
|
|
* <strong>{result?.label ?? ""}</strong>
|
|
* <button onClick={() => resolve({ value: event.value })}>Continue</button>
|
|
* </aside>
|
|
* ),
|
|
* });
|
|
*
|
|
* return <>{interruptElement}</>;
|
|
* }
|
|
* ```
|
|
*/
|
|
function useInterrupt(config) {
|
|
const { copilotkit } = require_CopilotKitProvider.useCopilotKit();
|
|
const { agent } = require_use_agent.useAgent({ agentId: config.agentId });
|
|
const [pendingEvent, setPendingEvent] = (0, react.useState)(null);
|
|
const [handlerResult, setHandlerResult] = (0, react.useState)(null);
|
|
(0, react.useEffect)(() => {
|
|
let localInterrupt = null;
|
|
const subscription = agent.subscribe({
|
|
onCustomEvent: ({ event }) => {
|
|
if (event.name === INTERRUPT_EVENT_NAME) localInterrupt = {
|
|
name: event.name,
|
|
value: event.value
|
|
};
|
|
},
|
|
onRunStartedEvent: () => {
|
|
localInterrupt = null;
|
|
setPendingEvent(null);
|
|
},
|
|
onRunFinalized: () => {
|
|
if (localInterrupt) {
|
|
setPendingEvent(localInterrupt);
|
|
localInterrupt = null;
|
|
}
|
|
},
|
|
onRunFailed: () => {
|
|
localInterrupt = null;
|
|
}
|
|
});
|
|
return () => subscription.unsubscribe();
|
|
}, [agent]);
|
|
const resolve = (0, react.useCallback)((response) => {
|
|
setPendingEvent(null);
|
|
copilotkit.runAgent({
|
|
agent,
|
|
forwardedProps: { command: { resume: response } }
|
|
});
|
|
}, [agent, copilotkit]);
|
|
(0, react.useEffect)(() => {
|
|
if (!pendingEvent) {
|
|
setHandlerResult(null);
|
|
return;
|
|
}
|
|
if (config.enabled && !config.enabled(pendingEvent)) {
|
|
setHandlerResult(null);
|
|
return;
|
|
}
|
|
const handler = config.handler;
|
|
if (!handler) {
|
|
setHandlerResult(null);
|
|
return;
|
|
}
|
|
let cancelled = false;
|
|
const maybePromise = handler({
|
|
event: pendingEvent,
|
|
resolve
|
|
});
|
|
if (isPromiseLike(maybePromise)) Promise.resolve(maybePromise).then((resolved) => {
|
|
if (!cancelled) setHandlerResult(resolved);
|
|
}).catch(() => {
|
|
if (!cancelled) setHandlerResult(null);
|
|
});
|
|
else setHandlerResult(maybePromise);
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [
|
|
pendingEvent,
|
|
config.enabled,
|
|
config.handler,
|
|
resolve
|
|
]);
|
|
const element = (0, react.useMemo)(() => {
|
|
if (!pendingEvent) return null;
|
|
if (config.enabled && !config.enabled(pendingEvent)) return null;
|
|
return config.render({
|
|
event: pendingEvent,
|
|
result: handlerResult,
|
|
resolve
|
|
});
|
|
}, [
|
|
pendingEvent,
|
|
handlerResult,
|
|
config.enabled,
|
|
config.render,
|
|
resolve
|
|
]);
|
|
(0, react.useEffect)(() => {
|
|
if (config.renderInChat === false) return;
|
|
copilotkit.setInterruptElement(element);
|
|
return () => copilotkit.setInterruptElement(null);
|
|
}, [
|
|
element,
|
|
config.renderInChat,
|
|
copilotkit
|
|
]);
|
|
if (config.renderInChat === false) return element;
|
|
}
|
|
|
|
//#endregion
|
|
exports.useInterrupt = useInterrupt;
|
|
//# sourceMappingURL=use-interrupt.cjs.map
|