Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions apps/docs/reference/copilotkit-core.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,6 @@ runAgent(params: CopilotKitCoreRunAgentParams): Promise<RunAgentResult>
```typescript
interface CopilotKitCoreRunAgentParams {
agent: AbstractAgent; // The agent to execute
withMessages?: Message[]; // Initial messages to send to the agent
agentId?: string; // Override the agent's default identifier
}
```

Expand Down
23 changes: 0 additions & 23 deletions packages/core/src/__tests__/core-basic-functionality.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,6 @@ describe("CopilotKitCore.runAgent - Basic Functionality", () => {
expect(agent.runAgentCalls[0].forwardedProps).toEqual({});
});

it("should pass withMessages to agent.addMessages", async () => {
const initialMessages = [createMessage({ content: "Initial" })];
const newMessages = [createAssistantMessage({ content: "Response" })];
const agent = new MockAgent({ newMessages });
copilotKitCore.addAgent__unsafe_dev_only({ id: "test", agent: agent as any });

await copilotKitCore.runAgent({ agent: agent as any, withMessages: initialMessages });

expect(agent.addMessages).toHaveBeenCalledWith(initialMessages);
expect(agent.messages).toContain(initialMessages[0]);
});

it("should work without withMessages parameter", async () => {
const newMessages = [createAssistantMessage({ content: "Response" })];
const agent = new MockAgent({ newMessages });
copilotKitCore.addAgent__unsafe_dev_only({ id: "test", agent: agent as any });

const result = await copilotKitCore.runAgent({ agent: agent as any });

expect(result.newMessages).toEqual(newMessages);
expect(agent.addMessages).not.toHaveBeenCalled();
});

it("should forward properties to agent.runAgent", async () => {
const properties = { apiKey: "test-key", model: "gpt-4" };
copilotKitCore = new CopilotKitCore({ properties });
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/__tests__/core-suggestions-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,9 +815,9 @@ describe("CopilotKitCore - Suggestions E2E", () => {
};

// Simulate user submitting a new message which triggers runAgent (and should clear/abort suggestions immediately)
consumerAgent.addMessages([createMessage({ content: "User follow-up" })]);
await copilotKitCore.runAgent({
agent: consumerAgent as any,
withMessages: [createMessage({ content: "User follow-up" })],
});

// The in-flight suggestion run should be aborted
Expand Down
9 changes: 1 addition & 8 deletions packages/core/src/core/run-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { FrontendTool } from "../types";

export interface CopilotKitCoreRunAgentParams {
agent: AbstractAgent;
withMessages?: Message[];
}

export interface CopilotKitCoreConnectAgentParams {
Expand Down Expand Up @@ -140,7 +139,7 @@ export class RunHandler {
/**
* Run an agent
*/
async runAgent({ agent, withMessages }: CopilotKitCoreRunAgentParams): Promise<RunAgentResult> {
async runAgent({ agent }: CopilotKitCoreRunAgentParams): Promise<RunAgentResult> {
// Agent ID is guaranteed to be set by validateAndAssignAgentId
if (agent.agentId) {
void (this.core as unknown as CopilotKitCoreFriendsAccess).suggestionEngine.clearSuggestions(agent.agentId);
Expand All @@ -150,9 +149,6 @@ export class RunHandler {
agent.headers = { ...(this.core as unknown as CopilotKitCoreFriendsAccess).headers };
}

if (withMessages) {
agent.addMessages(withMessages);
}
try {
const runAgentResult = await agent.runAgent(
{
Expand All @@ -169,9 +165,6 @@ export class RunHandler {
if (agent.agentId) {
context.agentId = agent.agentId;
}
if (withMessages) {
context.messageCount = withMessages.length;
}
await (this.core as unknown as CopilotKitCoreFriendsAccess).emitError({
error: runError,
code: CopilotKitCoreErrorCode.AGENT_RUN_FAILED,
Expand Down
146 changes: 146 additions & 0 deletions packages/react/src/hooks/__tests__/use-agent.e2e.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React from "react";
import { screen, fireEvent, waitFor } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { type BaseEvent, type RunAgentInput } from "@ag-ui/client";
import { Observable } from "rxjs";
import {
MockStepwiseAgent,
renderWithCopilotKit,
runStartedEvent,
runFinishedEvent,
textChunkEvent,
testId,
} from "@/__tests__/utils/test-helpers";
import { useAgent } from "../use-agent";
import { useCopilotKit } from "@/providers/CopilotKitProvider";
import { CopilotChat } from "@/components/chat/CopilotChat";

/**
* Mock agent that captures RunAgentInput to verify state is passed correctly
*/
class StateCapturingMockAgent extends MockStepwiseAgent {
public lastRunInput?: RunAgentInput;

run(input: RunAgentInput): Observable<BaseEvent> {
this.lastRunInput = input;
return super.run(input);
}
}

describe("useAgent e2e", () => {
describe("setState passes state to agent run", () => {
it("agent receives state set via setState when runAgent is called", async () => {
const agent = new StateCapturingMockAgent();

/**
* Test component that:
* 1. Gets agent via useAgent()
* 2. Gets copilotkit via useCopilotKit()
* 3. Sets state on agent and calls runAgent
*/
function StateTestComponent() {
const { agent: hookAgent } = useAgent();
const { copilotkit } = useCopilotKit();

const handleSetStateAndRun = async () => {
hookAgent.setState({ testKey: "testValue", counter: 42 });
await copilotkit.runAgent({ agent: hookAgent });
};

return (
<button data-testid="trigger-btn" onClick={handleSetStateAndRun}>
Set State and Run
</button>
);
}

renderWithCopilotKit({
agent,
children: <StateTestComponent />,
});

// Click the button to set state and trigger runAgent
const triggerBtn = await screen.findByTestId("trigger-btn");
fireEvent.click(triggerBtn);

// Wait for the agent's run method to be called
await waitFor(() => {
expect(agent.lastRunInput).toBeDefined();
});

// Complete the agent run
agent.emit(runStartedEvent());
agent.emit(runFinishedEvent());
agent.complete();

// Verify the state was passed to the agent
expect(agent.lastRunInput?.state).toEqual({
testKey: "testValue",
counter: 42,
});
});
});

describe("addMessage + runAgent displays in CopilotChat", () => {
it("messages added via useAgent show up in CopilotChat", async () => {
const agent = new MockStepwiseAgent();

/**
* Test component that:
* 1. Gets agent via useAgent()
* 2. Gets copilotkit via useCopilotKit()
* 3. Adds a user message and calls runAgent
*/
function MessageTestComponent() {
const { agent: hookAgent } = useAgent();
const { copilotkit } = useCopilotKit();

const handleAddMessageAndRun = async () => {
hookAgent.addMessage({
id: testId("user-msg"),
role: "user",
content: "Hello from useAgent!",
});
await copilotkit.runAgent({ agent: hookAgent });
};

return (
<div>
<button data-testid="send-btn" onClick={handleAddMessageAndRun}>
Send Message
</button>
<div style={{ height: 400 }}>
<CopilotChat />
</div>
</div>
);
}

renderWithCopilotKit({
agent,
children: <MessageTestComponent />,
});

// Click the button to add message and trigger runAgent
const sendBtn = await screen.findByTestId("send-btn");
fireEvent.click(sendBtn);

// User message should appear in the chat
await waitFor(() => {
expect(screen.getByText("Hello from useAgent!")).toBeDefined();
});

// Simulate agent response
const responseId = testId("assistant-msg");
agent.emit(runStartedEvent());
agent.emit(textChunkEvent(responseId, "Hello! I received your message."));
agent.emit(runFinishedEvent());
agent.complete();

// Assistant response should appear in the chat
await waitFor(() => {
expect(screen.getByText("Hello! I received your message.")).toBeDefined();
});
});
});
});
Loading