Skip to content

Commit 082db7a

Browse files
mmesuhasdeshpandearielweinberger
authored
Add CrewAI support (CopilotKit#1308)
Co-authored-by: Suhas Deshpande <suhasdeshpande@users.noreply.github.com> Co-authored-by: Ariel Weinberger <Weinberger.Ariel@gmail.com> Co-authored-by: Suhas Deshpande <suhas2u@gmail.com>
1 parent 5dab180 commit 082db7a

245 files changed

Lines changed: 47572 additions & 931 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/runtime": patch
3+
---
4+
5+
- Added RemoteAgentAdapter and implemented CopilotKit (protocol & events).
6+
- Integrated CrewAI (with a prototype, HITL, event system, and chat/demo tweaks).
7+
- Updated docs and cleaned up code (fixing stdout issues, restricting Python version, and streamlining demos).
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@copilotkit/runtime": minor
3+
---
4+
5+
- CrewAI support

CopilotKit/.vscode/cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"pino",
1818
"langsmith",
1919
"readables",
20-
"uuidv4"
20+
"uuidv4",
21+
"crewai"
2122
]
2223
}

CopilotKit/packages/runtime/src/graphql/resolvers/copilot.resolver.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ export class CopilotResolver {
196196
rejectOutputMessagesPromise = reject;
197197
});
198198

199+
if (copilotCloudPublicApiKey) {
200+
ctx.properties["copilotCloudPublicApiKey"] = copilotCloudPublicApiKey;
201+
}
202+
199203
logger.debug("Processing");
200204
const {
201205
eventSource,

CopilotKit/packages/runtime/src/lib/runtime/__tests__/remote-action-constructors.test.ts

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import "reflect-metadata";
12
import { TextEncoder } from "util";
23
import { RemoteLangGraphEventSource } from "../../../agents/langgraph/event-source";
34
import telemetry from "../../telemetry-client";
@@ -7,6 +8,7 @@ import {
78
createHeaders,
89
} from "../remote-action-constructors";
910
import { execute } from "../remote-lg-action";
11+
import { ReplaySubject } from "rxjs";
1012

1113
// Mock external dependencies
1214
jest.mock("../remote-lg-action", () => ({
@@ -49,7 +51,7 @@ beforeEach(() => {
4951

5052
describe("remote action constructors", () => {
5153
describe("constructLGCRemoteAction", () => {
52-
it("should create an agent with langGraphAgentHandler that processes events", async () => {
54+
it("should create an agent with remoteAgentHandler that processes events", async () => {
5355
// Arrange: simulate execute returning a dummy ReadableStream
5456
const dummyEncodedEvent = new TextEncoder().encode(JSON.stringify({ event: "test" }) + "\n");
5557
const readerMock = {
@@ -72,7 +74,7 @@ describe("remote action constructors", () => {
7274
processLangGraphEvents: processLangGraphEventsMock,
7375
}));
7476

75-
// Act: build the action and call langGraphAgentHandler
77+
// Act: build the action and call remoteAgentHandler
7678
const actions = constructLGCRemoteAction({
7779
endpoint,
7880
graphqlContext,
@@ -84,7 +86,7 @@ describe("remote action constructors", () => {
8486
const action = actions[0];
8587
expect(action.name).toEqual(dummyAgent.name);
8688

87-
const result = await action.langGraphAgentHandler({
89+
const result = await action.remoteAgentHandler({
8890
name: dummyAgent.name,
8991
actionInputsWithoutAgents: [],
9092
threadId: "thread1",
@@ -160,26 +162,46 @@ describe("remote action constructors", () => {
160162
});
161163

162164
it("should create remote agent handler that processes events", async () => {
163-
// Arrange: mock fetch for agent handler to return a dummy stream
164-
const dummyEncodedAgentEvent = new TextEncoder().encode('{"event":"data"}\n');
165-
const agentReaderMock = {
166-
read: jest
167-
.fn()
168-
.mockResolvedValueOnce({ done: false, value: dummyEncodedAgentEvent })
169-
.mockResolvedValueOnce({ done: true, value: new Uint8Array() }),
170-
};
171-
const dummyStreamResponse = {
172-
getReader: () => agentReaderMock,
165+
const json = {
166+
agents: [
167+
{
168+
name: "agent2",
169+
description: "agent desc",
170+
type: "langgraph", // Add type to match RemoteAgentType.LangGraph
171+
},
172+
],
173+
actions: [
174+
{
175+
name: "action1",
176+
description: "action desc",
177+
parameters: { param: "value" },
178+
},
179+
],
173180
};
174-
global.fetch = jest.fn().mockResolvedValue({
175-
ok: true,
176-
text: jest.fn().mockResolvedValue("ok"),
177-
body: dummyStreamResponse,
178-
});
179181

180-
const processLangGraphEventsMock = jest.fn(() => "agent events processed");
182+
const dummyEncodedAgentEvent = new TextEncoder().encode('{"type":"data","content":"test"}\n');
183+
184+
const mockResponse = new Response(
185+
new ReadableStream({
186+
start(controller) {
187+
controller.enqueue(dummyEncodedAgentEvent);
188+
controller.close();
189+
},
190+
}),
191+
{
192+
status: 200,
193+
statusText: "OK",
194+
headers: new Headers({
195+
"content-type": "application/json",
196+
}),
197+
},
198+
);
199+
200+
global.fetch = jest.fn().mockResolvedValue(mockResponse);
201+
202+
const processLangGraphEventsMock = jest.fn().mockResolvedValue("agent events processed");
181203
(RemoteLangGraphEventSource as jest.Mock).mockImplementation(() => ({
182-
eventStream$: { next: jest.fn(), complete: jest.fn(), error: jest.fn() },
204+
eventStream$: new ReplaySubject(),
183205
processLangGraphEvents: processLangGraphEventsMock,
184206
}));
185207

@@ -192,29 +214,17 @@ describe("remote action constructors", () => {
192214
messages: [],
193215
agentStates,
194216
});
195-
// The remote agent is the second item in the array
196-
expect(actionsArray).toHaveLength(2);
197-
const remoteAgentHandler = (actionsArray[1] as any).langGraphAgentHandler;
217+
218+
const remoteAgentHandler = (actionsArray[1] as any).remoteAgentHandler;
198219
const result = await remoteAgentHandler({
199220
name: "agent2",
200221
actionInputsWithoutAgents: [],
201222
threadId: "thread2",
202223
nodeName: "node2",
203-
additionalMessages: [],
204-
metaEvents: [],
205224
});
225+
206226
expect(processLangGraphEventsMock).toHaveBeenCalled();
207227
expect(result).toBe("agent events processed");
208-
209-
// Check telemetry.capture for agent execution
210-
expect(telemetry.capture).toHaveBeenCalledWith(
211-
"oss.runtime.remote_action_executed",
212-
expect.objectContaining({
213-
agentExecution: true,
214-
type: "self-hosted",
215-
agentsAmount: 1,
216-
}),
217-
);
218228
});
219229
});
220230

CopilotKit/packages/runtime/src/lib/runtime/copilot-runtime.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ import { Message } from "../../graphql/types/converted";
3939
import { ForwardedParametersInput } from "../../graphql/inputs/forwarded-parameters.input";
4040

4141
import {
42-
isLangGraphAgentAction,
43-
LangGraphAgentAction,
42+
isRemoteAgentAction,
43+
RemoteAgentAction,
4444
EndpointType,
4545
setupRemoteActions,
4646
EndpointDefinition,
@@ -305,7 +305,7 @@ please use an LLM adapter instead.`,
305305
(action) =>
306306
// TODO-AGENTS: do not exclude ALL server side actions
307307
!serverSideActions.find((serverSideAction) => serverSideAction.name == action.name),
308-
// !isLangGraphAgentAction(
308+
// !isRemoteAgentAction(
309309
// serverSideActions.find((serverSideAction) => serverSideAction.name == action.name),
310310
// ),
311311
),
@@ -322,7 +322,6 @@ please use an LLM adapter instead.`,
322322
}
323323

324324
async discoverAgentsFromEndpoints(graphqlContext: GraphQLContext): Promise<AgentWithEndpoint[]> {
325-
const headers = createHeaders(null, graphqlContext);
326325
const agents = this.remoteEndpointDefinitions.reduce(
327326
async (acc: Promise<Agent[]>, endpoint) => {
328327
const agents = await acc;
@@ -355,12 +354,12 @@ please use an LLM adapter instead.`,
355354
description: string;
356355
}>;
357356
}
358-
359-
const fetchUrl = `${(endpoint as CopilotKitEndpoint).url}/info`;
357+
const cpkEndpoint = endpoint as CopilotKitEndpoint;
358+
const fetchUrl = `${endpoint.url}/info`;
360359
try {
361360
const response = await fetch(fetchUrl, {
362361
method: "POST",
363-
headers,
362+
headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
364363
body: JSON.stringify({ properties: graphqlContext.properties }),
365364
});
366365
if (!response.ok) {
@@ -444,11 +443,12 @@ please use an LLM adapter instead.`,
444443
agentWithEndpoint.endpoint.type === EndpointType.CopilotKit ||
445444
!("type" in agentWithEndpoint.endpoint)
446445
) {
447-
const fetchUrl = `${(agentWithEndpoint.endpoint as CopilotKitEndpoint).url}/agents/state`;
446+
const cpkEndpoint = agentWithEndpoint.endpoint as CopilotKitEndpoint;
447+
const fetchUrl = `${cpkEndpoint.url}/agents/state`;
448448
try {
449449
const response = await fetch(fetchUrl, {
450450
method: "POST",
451-
headers,
451+
headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
452452
body: JSON.stringify({
453453
properties: graphqlContext.properties,
454454
threadId,
@@ -505,8 +505,8 @@ please use an LLM adapter instead.`,
505505
const messages = convertGqlInputToMessages(rawMessages);
506506

507507
const currentAgent = serverSideActions.find(
508-
(action) => action.name === agentName && isLangGraphAgentAction(action),
509-
) as LangGraphAgentAction;
508+
(action) => action.name === agentName && isRemoteAgentAction(action),
509+
) as RemoteAgentAction;
510510

511511
if (!currentAgent) {
512512
throw new CopilotKitAgentDiscoveryError({ agentName });
@@ -519,9 +519,9 @@ please use an LLM adapter instead.`,
519519
.filter(
520520
(action) =>
521521
// Case 1: Keep all regular (non-agent) actions
522-
!isLangGraphAgentAction(action) ||
522+
!isRemoteAgentAction(action) ||
523523
// Case 2: For agent actions, keep all except self (prevent infinite loops)
524-
(isLangGraphAgentAction(action) && action.name !== agentName) /* prevent self-calls */,
524+
(isRemoteAgentAction(action) && action.name !== agentName) /* prevent self-calls */,
525525
)
526526
.map((action) => ({
527527
name: action.name,
@@ -542,7 +542,7 @@ please use an LLM adapter instead.`,
542542
});
543543
try {
544544
const eventSource = new RuntimeEventSource();
545-
const stream = await currentAgent.langGraphAgentHandler({
545+
const stream = await currentAgent.remoteAgentHandler({
546546
name: agentName,
547547
threadId,
548548
nodeName,

0 commit comments

Comments
 (0)