forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuse-langgraph-interrupt.ts
More file actions
103 lines (95 loc) · 3.27 KB
/
Copy pathuse-langgraph-interrupt.ts
File metadata and controls
103 lines (95 loc) · 3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import React, { useCallback, useRef } from "react";
import { LangGraphInterruptRender } from "../types/interrupt-action";
import { useInterrupt, useCopilotChatConfiguration } from "../v2";
import type {
InterruptEvent,
InterruptRenderProps,
InterruptHandlerProps,
} from "../v2";
import { MetaEventName } from "@copilotkit/runtime-client-gql";
import { parseJson } from "@copilotkit/shared";
import { useAgentNodeName } from "./use-agent-nodename";
import type { AgentSession } from "../context/copilot-context";
/**
* Transforms a v2 InterruptEvent into the v1 LangGraphInterruptEvent shape
* expected by existing useLangGraphInterrupt callbacks.
*/
function toV1Event<TEventValue>(event: InterruptEvent<TEventValue>) {
const value =
typeof event.value === "string"
? parseJson(event.value, event.value)
: event.value;
return {
name: MetaEventName.LangGraphInterruptEvent,
type: "MetaEvent" as const,
value,
};
}
export function useLangGraphInterrupt<TEventValue = any>(
action: Omit<LangGraphInterruptRender<TEventValue>, "id">,
_dependencies?: any[],
) {
const actionRef = useRef(action);
// Update ref synchronously during render so it's always current
// when callbacks read from it (useEffect would be one tick late).
actionRef.current = action;
const existingConfig = useCopilotChatConfiguration();
const resolvedAgentId =
action.agentId ?? existingConfig?.agentId ?? "default";
const threadId = existingConfig?.threadId;
const nodeName = useAgentNodeName(resolvedAgentId);
// Keep agentMetadata in a ref so stable callbacks always see current values.
const metadataRef = useRef<AgentSession>({
agentName: resolvedAgentId,
threadId,
nodeName,
});
metadataRef.current = {
agentName: resolvedAgentId,
threadId,
nodeName,
};
// Stable callback references that always read the latest action from the ref.
// This prevents useInterrupt's internal useMemo/useEffect from seeing new
// function identities on every render, which would cause an infinite loop.
const render = useCallback(
({ event, result, resolve }: InterruptRenderProps<TEventValue>) => {
const renderFn = actionRef.current.render;
if (!renderFn) return React.createElement(React.Fragment);
const rendered = renderFn({
event: toV1Event(event) as any,
result,
resolve: (r) => resolve(r),
});
if (typeof rendered === "string") {
return React.createElement(React.Fragment, null, rendered);
}
return rendered;
},
[],
);
// Handler always delegates to the ref — if no handler is set at call time,
// the optional chaining returns undefined which useInterrupt treats as null.
const handler = useCallback(
({ event, resolve }: InterruptHandlerProps<TEventValue>) => {
return actionRef.current.handler?.({
event: toV1Event(event) as any,
resolve: (r) => resolve(r),
});
},
[],
);
const enabled = useCallback((event: InterruptEvent<TEventValue>) => {
if (!actionRef.current.enabled) return true;
return actionRef.current.enabled({
eventValue: toV1Event(event).value,
agentMetadata: metadataRef.current,
});
}, []);
useInterrupt({
render,
handler,
enabled,
agentId: resolvedAgentId,
});
}