Skip to content

Commit 926499b

Browse files
authored
Load agent state (CopilotKit#1251)
1 parent 07b26f8 commit 926499b

168 files changed

Lines changed: 4730 additions & 3549 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@copilotkit/react-core": patch
3+
"@copilotkit/runtime-client-gql": patch
4+
"@copilotkit/runtime": patch
5+
---
6+
7+
- Load the previous state of an agent if `threadId` is provided to CopilotKit, including all messages

CopilotKit/packages/react-core/src/components/copilot-provider/copilot-messages.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,36 @@
22
* An internal context to separate the messages state (which is constantly changing) from the rest of CopilotKit context
33
*/
44

5-
import { useState } from "react";
5+
import { useEffect, useState } from "react";
66
import { CopilotMessagesContext } from "../../context/copilot-messages-context";
7-
import { Message } from "@copilotkit/runtime-client-gql";
7+
import { loadMessagesFromJsonRepresentation, Message } from "@copilotkit/runtime-client-gql";
88
import { CopilotKitProps } from "./copilotkit-props";
9+
import { useCopilotContext } from "../../context/copilot-context";
910

1011
export function CopilotMessages({ children, ...props }: CopilotKitProps) {
1112
const [messages, setMessages] = useState<Message[]>([]);
1213

14+
const { threadId, agentSession, runtimeClient } = useCopilotContext();
15+
16+
useEffect(() => {
17+
if (agentSession?.agentName) {
18+
// reload messages
19+
const fetchAgentState = async () => {
20+
const result = await runtimeClient.loadAgentState({
21+
threadId,
22+
agentName: agentSession.agentName,
23+
});
24+
if (result.data?.loadAgentState?.threadExists) {
25+
const messages = loadMessagesFromJsonRepresentation(
26+
JSON.parse(result.data?.loadAgentState?.messages || "[]"),
27+
);
28+
setMessages(messages);
29+
}
30+
};
31+
void fetchAgentState();
32+
}
33+
}, [threadId, agentSession?.agentName !== undefined]);
34+
1335
return (
1436
<CopilotMessagesContext.Provider
1537
value={{

CopilotKit/packages/react-core/src/components/copilot-provider/copilotkit-props.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,9 @@ export interface CopilotKitProps {
9494
onSignInComplete: (authState: AuthState) => void;
9595
}>;
9696
};
97+
98+
/**
99+
* The thread id to use for the CopilotKit.
100+
*/
101+
threadId?: string;
97102
}

CopilotKit/packages/react-core/src/components/copilot-provider/copilotkit.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* ```
1515
*/
1616

17-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
17+
import { useCallback, useEffect, useMemo, useRef, useState, SetStateAction } from "react";
1818
import {
1919
CopilotContext,
2020
CopilotApiConfig,
@@ -30,6 +30,7 @@ import {
3030
CopilotCloudConfig,
3131
FunctionCallHandler,
3232
COPILOT_CLOUD_PUBLIC_API_KEY_HEADER,
33+
randomUUID,
3334
} from "@copilotkit/shared";
3435

3536
import { FrontendAction } from "../../types/frontend-action";
@@ -42,7 +43,11 @@ import { ToastProvider } from "../toast/toast-provider";
4243
import { useCopilotRuntimeClient } from "../../hooks/use-copilot-runtime-client";
4344
import { shouldShowDevConsole } from "../../utils";
4445
import { CopilotErrorBoundary } from "../error-boundary/error-boundary";
45-
import { Agent } from "@copilotkit/runtime-client-gql";
46+
import {
47+
Agent,
48+
ExtensionsInput,
49+
loadMessagesFromJsonRepresentation,
50+
} from "@copilotkit/runtime-client-gql";
4651

4752
export function CopilotKit({ children, ...props }: CopilotKitProps) {
4853
const showDevConsole = props.showDevConsole === undefined ? "auto" : props.showDevConsole;
@@ -80,6 +85,7 @@ export function CopilotKitInternal({ children, ...props }: CopilotKitProps) {
8085
const [isLoading, setIsLoading] = useState(false);
8186
const [chatInstructions, setChatInstructions] = useState("");
8287
const [authStates, setAuthStates] = useState<Record<string, AuthState>>({});
88+
const [extensions, setExtensions] = useState<ExtensionsInput>({});
8389

8490
const {
8591
addElement: addDocument,
@@ -314,7 +320,25 @@ export function CopilotKitInternal({ children, ...props }: CopilotKitProps) {
314320
}
315321

316322
const [agentSession, setAgentSession] = useState<AgentSession | null>(initialAgentSession);
317-
const [threadId, setThreadId] = useState<string | null>(null);
323+
324+
const [internalThreadId, setInternalThreadId] = useState<string>(props.threadId || randomUUID());
325+
const setThreadId = useCallback(
326+
(value: SetStateAction<string>) => {
327+
if (props.threadId) {
328+
throw new Error("Cannot call setThreadId() when threadId is provided via props.");
329+
}
330+
setInternalThreadId(value);
331+
},
332+
[props.threadId],
333+
);
334+
335+
// update the internal threadId if the props.threadId changes
336+
useEffect(() => {
337+
if (props.threadId !== undefined) {
338+
setInternalThreadId(props.threadId);
339+
}
340+
}, [props.threadId]);
341+
318342
const [runId, setRunId] = useState<string | null>(null);
319343

320344
const chatAbortControllerRef = useRef<AbortController | null>(null);
@@ -356,7 +380,7 @@ export function CopilotKitInternal({ children, ...props }: CopilotKitProps) {
356380
runtimeClient,
357381
forwardedParameters: props.forwardedParameters || {},
358382
agentLock: props.agent || null,
359-
threadId,
383+
threadId: internalThreadId,
360384
setThreadId,
361385
runId,
362386
setRunId,
@@ -365,6 +389,8 @@ export function CopilotKitInternal({ children, ...props }: CopilotKitProps) {
365389
authConfig: props.authConfig,
366390
authStates,
367391
setAuthStates,
392+
extensions,
393+
setExtensions,
368394
}}
369395
>
370396
<CopilotMessages>{children}</CopilotMessages>

CopilotKit/packages/react-core/src/context/copilot-context.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import { DocumentPointer } from "../types";
1010
import { CopilotChatSuggestionConfiguration } from "../types/chat-suggestion-configuration";
1111
import { CoAgentStateRender, CoAgentStateRenderProps } from "../types/coagent-action";
1212
import { CoagentState } from "../types/coagent-state";
13-
import { CopilotRuntimeClient, ForwardedParametersInput } from "@copilotkit/runtime-client-gql";
13+
import {
14+
CopilotRuntimeClient,
15+
ExtensionsInput,
16+
ForwardedParametersInput,
17+
} from "@copilotkit/runtime-client-gql";
1418
import { Agent } from "@copilotkit/runtime-client-gql";
1519

1620
/**
@@ -160,8 +164,8 @@ export interface CopilotContextParams {
160164

161165
agentLock: string | null;
162166

163-
threadId: string | null;
164-
setThreadId: React.Dispatch<React.SetStateAction<string | null>>;
167+
threadId: string;
168+
setThreadId: React.Dispatch<React.SetStateAction<string>>;
165169

166170
runId: string | null;
167171
setRunId: React.Dispatch<React.SetStateAction<string | null>>;
@@ -193,6 +197,9 @@ export interface CopilotContextParams {
193197
onSignInComplete: (authState: AuthState) => void;
194198
}>;
195199
};
200+
201+
extensions: ExtensionsInput;
202+
setExtensions: React.Dispatch<React.SetStateAction<ExtensionsInput>>;
196203
}
197204

198205
const emptyCopilotContext: CopilotContextParams = {
@@ -248,12 +255,14 @@ const emptyCopilotContext: CopilotContextParams = {
248255
setAgentSession: () => {},
249256
forwardedParameters: {},
250257
agentLock: null,
251-
threadId: null,
258+
threadId: "",
252259
setThreadId: () => {},
253260
runId: null,
254261
setRunId: () => {},
255262
chatAbortControllerRef: { current: null },
256263
availableAgents: [],
264+
extensions: {},
265+
setExtensions: () => {},
257266
};
258267

259268
export const CopilotContext = React.createContext<CopilotContextParams>(emptyCopilotContext);

CopilotKit/packages/react-core/src/hooks/use-chat.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
CopilotRequestType,
2020
ForwardedParametersInput,
2121
loadMessagesFromJsonRepresentation,
22+
ExtensionsInput,
23+
CopilotRuntimeClient,
2224
} from "@copilotkit/runtime-client-gql";
2325

2426
import { CopilotApiConfig } from "../context";
@@ -107,11 +109,11 @@ export type UseChatOptions = {
107109
/**
108110
* The current thread ID.
109111
*/
110-
threadId: string | null;
112+
threadId: string;
111113
/**
112114
* set the current thread ID
113115
*/
114-
setThreadId: (threadId: string | null) => void;
116+
setThreadId: (threadId: string) => void;
115117
/**
116118
* The current run ID.
117119
*/
@@ -128,6 +130,14 @@ export type UseChatOptions = {
128130
* The agent lock.
129131
*/
130132
agentLock: string | null;
133+
/**
134+
* The extensions.
135+
*/
136+
extensions: ExtensionsInput;
137+
/**
138+
* The setState-powered method to update the extensions.
139+
*/
140+
setExtensions: React.Dispatch<React.SetStateAction<ExtensionsInput>>;
131141
};
132142

133143
export type UseChatHelpers = {
@@ -183,6 +193,8 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
183193
setRunId,
184194
chatAbortControllerRef,
185195
agentLock,
196+
extensions,
197+
setExtensions,
186198
} = options;
187199
const runChatCompletionRef = useRef<(previousMessages: Message[]) => Promise<Message[]>>();
188200
const addErrorToast = useErrorToast();
@@ -191,10 +203,11 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
191203
// This is a workaround and needs to be addressed in the future
192204
const agentSessionRef = useRef<AgentSession | null>(agentSession);
193205
agentSessionRef.current = agentSession;
194-
const threadIdRef = useRef<string | null>(threadId);
195-
threadIdRef.current = threadId;
206+
196207
const runIdRef = useRef<string | null>(runId);
197208
runIdRef.current = runId;
209+
const extensionsRef = useRef<ExtensionsInput>(extensions);
210+
extensionsRef.current = extensions;
198211

199212
const publicApiKey = copilotConfig.publicApiKey;
200213

@@ -240,8 +253,9 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
240253
actions: processActionsForRuntimeRequest(actions),
241254
url: window.location.href,
242255
},
243-
threadId: threadIdRef.current,
256+
threadId: threadId,
244257
runId: runIdRef.current,
258+
extensions: extensionsRef.current,
245259
messages: convertMessagesToGqlInput(filterAgentStateMessages(messagesWithContext)),
246260
...(copilotConfig.cloud
247261
? {
@@ -314,11 +328,17 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
314328
continue;
315329
}
316330

317-
threadIdRef.current = value.generateCopilotResponse.threadId || null;
318331
runIdRef.current = value.generateCopilotResponse.runId || null;
319332

320-
setThreadId(threadIdRef.current);
333+
// in the output, graphql inserts __typename, which leads to an error when sending it along
334+
// as input to the next request.
335+
extensionsRef.current = CopilotRuntimeClient.removeGraphQLTypename(
336+
value.generateCopilotResponse.extensions || {},
337+
);
338+
339+
// setThreadId(threadIdRef.current);
321340
setRunId(runIdRef.current);
341+
setExtensions(extensionsRef.current);
322342

323343
messages = convertGqlOutputToMessages(
324344
filterAdjacentAgentStateMessages(value.generateCopilotResponse.messages),

CopilotKit/packages/react-core/src/hooks/use-coagent.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ import {
9797
} from "../context";
9898
import { CoagentState } from "../types/coagent-state";
9999
import { useCopilotChat } from "./use-copilot-chat";
100-
import { Message } from "@copilotkit/runtime-client-gql";
100+
import { loadMessagesFromJsonRepresentation, Message } from "@copilotkit/runtime-client-gql";
101101
import { flushSync } from "react-dom";
102102
import { useAsyncCallback } from "../components/error-boundary/error-utils";
103103
import { useToast } from "../components/toast/toast-provider";
104+
import { useCopilotRuntimeClient } from "./use-copilot-runtime-client";
104105

105106
interface WithInternalStateManagementAndInitial<T> {
106107
/**
@@ -230,7 +231,8 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
230231

231232
const messagesContext = useCopilotMessagesContext();
232233
const context = { ...generalContext, ...messagesContext };
233-
const { coagentStates, coagentStatesRef, setCoagentStatesWithRef } = context;
234+
const { coagentStates, coagentStatesRef, setCoagentStatesWithRef, threadId, copilotApiConfig } =
235+
context;
234236
const { appendMessage, runChatCompletion } = useCopilotChat();
235237

236238
const getCoagentState = (coagentStates: Record<string, CoagentState>, name: string) => {
@@ -249,6 +251,12 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
249251
}
250252
};
251253

254+
const runtimeClient = useCopilotRuntimeClient({
255+
url: copilotApiConfig.chatApiEndpoint,
256+
publicApiKey: copilotApiConfig.publicApiKey,
257+
credentials: copilotApiConfig.credentials,
258+
});
259+
252260
// if we manage state internally, we need to provide a function to set the state
253261
const setState = (newState: T | ((prevState: T | undefined) => T)) => {
254262
let coagentState: CoagentState = getCoagentState(coagentStatesRef.current || {}, name);
@@ -264,9 +272,27 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
264272
});
265273
};
266274

267-
const coagentState = getCoagentState(coagentStates, name);
275+
useEffect(() => {
276+
const fetchAgentState = async () => {
277+
const result = await runtimeClient.loadAgentState({
278+
threadId: threadId,
279+
agentName: name,
280+
});
281+
if (
282+
result.data?.loadAgentState?.threadExists &&
283+
result.data?.loadAgentState?.state &&
284+
result.data?.loadAgentState?.state != "{}"
285+
) {
286+
const fetchedState = JSON.parse(result.data?.loadAgentState?.state);
287+
isExternalStateManagement(options)
288+
? options.setState(fetchedState)
289+
: setState(fetchedState);
290+
}
291+
};
292+
void fetchAgentState();
293+
}, [threadId]);
268294

269-
const state = isExternalStateManagement(options) ? options.state : coagentState.state;
295+
const coagentState = getCoagentState(coagentStates, name);
270296

271297
// Sync internal state with external state if state management is external
272298
useEffect(() => {

CopilotKit/packages/react-core/src/hooks/use-copilot-chat.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import { Message, Role, TextMessage } from "@copilotkit/runtime-client-gql";
4747
import { SystemMessageFunction } from "../types";
4848
import { useChat, AppendMessageOptions } from "./use-chat";
4949
import { defaultCopilotContextCategories } from "../components";
50-
import { CoAgentStateRenderHandlerArguments } from "@copilotkit/shared";
50+
import { CoAgentStateRenderHandlerArguments, randomId } from "@copilotkit/shared";
5151
import { useCopilotMessagesContext } from "../context";
5252
import { useAsyncCallback } from "../components/error-boundary/error-utils";
5353

@@ -110,6 +110,8 @@ export function useCopilotChat({
110110
runId,
111111
setRunId,
112112
chatAbortControllerRef,
113+
extensions,
114+
setExtensions,
113115
} = useCopilotContext();
114116
const { messages, setMessages } = useCopilotMessagesContext();
115117

@@ -175,6 +177,8 @@ export function useCopilotChat({
175177
setRunId,
176178
chatAbortControllerRef,
177179
agentLock,
180+
extensions,
181+
setExtensions,
178182
});
179183

180184
// this is a workaround born out of a bug that Athena incessantly ran into.
@@ -227,7 +231,6 @@ export function useCopilotChat({
227231
const reset = useCallback(() => {
228232
latestStopFunc();
229233
setMessages([]);
230-
setThreadId(null);
231234
setRunId(null);
232235
setCoagentStatesWithRef({});
233236
let initialAgentSession: AgentSession | null = null;

0 commit comments

Comments
 (0)