Skip to content

Commit e6ebae2

Browse files
Update Node tests
1 parent 1b770f3 commit e6ebae2

6 files changed

Lines changed: 66 additions & 114 deletions

File tree

nodejs/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010

1111
export { CopilotClient } from "./client.js";
12-
export { CopilotSession } from "./session.js";
12+
export { CopilotSession, type AssistantMessageEvent } from "./session.js";
1313
export { defineTool } from "./types.js";
1414
export type {
1515
ConnectionState,

nodejs/src/session.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ import type {
1919
ToolHandler,
2020
} from "./types.js";
2121

22+
/** Assistant message event - the final response from the assistant. */
23+
export type AssistantMessageEvent = Extract<SessionEvent, { type: "assistant.message" }>;
24+
2225
/**
2326
* Represents a single conversation session with the Copilot CLI.
2427
*
@@ -113,21 +116,29 @@ export class CopilotSession {
113116
* console.log(response?.data.content); // "4"
114117
* ```
115118
*/
116-
async sendAndWait(options: MessageOptions, timeout?: number): Promise<SessionEvent | undefined> {
119+
async sendAndWait(options: MessageOptions, timeout?: number): Promise<AssistantMessageEvent | undefined> {
117120
const effectiveTimeout = timeout ?? 60_000;
118121

119122
let resolveIdle: () => void;
120-
const idlePromise = new Promise<void>((resolve) => {
123+
let rejectWithError: (error: Error) => void;
124+
const idlePromise = new Promise<void>((resolve, reject) => {
121125
resolveIdle = resolve;
126+
rejectWithError = reject;
122127
});
123128

124-
let lastAssistantMessage: SessionEvent | undefined;
129+
let lastAssistantMessage: AssistantMessageEvent | undefined;
125130

131+
// Register event handler BEFORE calling send to avoid race condition
132+
// where session.idle fires before we start listening
126133
const unsubscribe = this.on((event) => {
127134
if (event.type === "assistant.message") {
128135
lastAssistantMessage = event;
129136
} else if (event.type === "session.idle") {
130137
resolveIdle();
138+
} else if (event.type === "session.error") {
139+
const error = new Error(event.data.message);
140+
error.stack = event.data.stack;
141+
rejectWithError(error);
131142
}
132143
});
133144

nodejs/test/e2e/mcp-and-agents.test.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import { describe, expect, it } from "vitest";
66
import type { CustomAgentConfig, MCPLocalServerConfig, MCPServerConfig } from "../../src/index.js";
77
import { createSdkTestContext } from "./harness/sdkTestContext.js";
8-
import { getFinalAssistantMessage } from "./harness/sdkTestHelper.js";
98

109
describe("MCP Servers and Custom Agents", async () => {
1110
const { copilotClient: client } = await createSdkTestContext();
@@ -28,11 +27,9 @@ describe("MCP Servers and Custom Agents", async () => {
2827
expect(session.sessionId).toBeDefined();
2928

3029
// Simple interaction to verify session works
31-
await session.send({
30+
const message = await session.sendAndWait({
3231
prompt: "What is 2+2?",
3332
});
34-
35-
const message = await getFinalAssistantMessage(session);
3633
expect(message?.data.content).toContain("4");
3734

3835
await session.destroy();
@@ -42,8 +39,7 @@ describe("MCP Servers and Custom Agents", async () => {
4239
// Create a session first
4340
const session1 = await client.createSession();
4441
const sessionId = session1.sessionId;
45-
await session1.send({ prompt: "What is 1+1?" });
46-
await getFinalAssistantMessage(session1);
42+
await session1.sendAndWait({ prompt: "What is 1+1?" });
4743

4844
// Resume with MCP servers
4945
const mcpServers: Record<string, MCPServerConfig> = {
@@ -61,11 +57,9 @@ describe("MCP Servers and Custom Agents", async () => {
6157

6258
expect(session2.sessionId).toBe(sessionId);
6359

64-
await session2.send({
60+
const message = await session2.sendAndWait({
6561
prompt: "What is 3+3?",
6662
});
67-
68-
const message = await getFinalAssistantMessage(session2);
6963
expect(message?.data.content).toContain("6");
7064

7165
await session2.destroy();
@@ -115,11 +109,9 @@ describe("MCP Servers and Custom Agents", async () => {
115109
expect(session.sessionId).toBeDefined();
116110

117111
// Simple interaction to verify session works
118-
await session.send({
112+
const message = await session.sendAndWait({
119113
prompt: "What is 5+5?",
120114
});
121-
122-
const message = await getFinalAssistantMessage(session);
123115
expect(message?.data.content).toContain("10");
124116

125117
await session.destroy();
@@ -129,8 +121,7 @@ describe("MCP Servers and Custom Agents", async () => {
129121
// Create a session first
130122
const session1 = await client.createSession();
131123
const sessionId = session1.sessionId;
132-
await session1.send({ prompt: "What is 1+1?" });
133-
await getFinalAssistantMessage(session1);
124+
await session1.sendAndWait({ prompt: "What is 1+1?" });
134125

135126
// Resume with custom agents
136127
const customAgents: CustomAgentConfig[] = [
@@ -148,11 +139,9 @@ describe("MCP Servers and Custom Agents", async () => {
148139

149140
expect(session2.sessionId).toBe(sessionId);
150141

151-
await session2.send({
142+
const message = await session2.sendAndWait({
152143
prompt: "What is 6+6?",
153144
});
154-
155-
const message = await getFinalAssistantMessage(session2);
156145
expect(message?.data.content).toContain("12");
157146

158147
await session2.destroy();
@@ -257,11 +246,9 @@ describe("MCP Servers and Custom Agents", async () => {
257246

258247
expect(session.sessionId).toBeDefined();
259248

260-
await session.send({
249+
const message = await session.sendAndWait({
261250
prompt: "What is 7+7?",
262251
});
263-
264-
const message = await getFinalAssistantMessage(session);
265252
expect(message?.data.content).toContain("14");
266253

267254
await session.destroy();

nodejs/test/e2e/permissions.test.ts

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { join } from "path";
77
import { describe, expect, it } from "vitest";
88
import type { PermissionRequest, PermissionRequestResult } from "../../src/index.js";
99
import { createSdkTestContext } from "./harness/sdkTestContext.js";
10-
import { getFinalAssistantMessage } from "./harness/sdkTestHelper.js";
1110

1211
describe("Permission callbacks", async () => {
1312
const { copilotClient: client, workDir } = await createSdkTestContext();
@@ -28,12 +27,10 @@ describe("Permission callbacks", async () => {
2827

2928
await writeFile(join(workDir, "test.txt"), "original content");
3029

31-
await session.send({
30+
await session.sendAndWait({
3231
prompt: "Edit test.txt and replace 'original' with 'modified'",
3332
});
3433

35-
await getFinalAssistantMessage(session);
36-
3734
// Should have received at least one permission request
3835
expect(permissionRequests.length).toBeGreaterThan(0);
3936

@@ -55,12 +52,10 @@ describe("Permission callbacks", async () => {
5552
const testFile = join(workDir, "protected.txt");
5653
await writeFile(testFile, originalContent);
5754

58-
await session.send({
55+
await session.sendAndWait({
5956
prompt: "Edit protected.txt and replace 'protected' with 'hacked'.",
6057
});
6158

62-
await getFinalAssistantMessage(session);
63-
6459
// Verify the file was NOT modified
6560
const content = await readFile(testFile, "utf-8");
6661
expect(content).toBe(originalContent);
@@ -72,11 +67,9 @@ describe("Permission callbacks", async () => {
7267
// Create session without onPermissionRequest handler
7368
const session = await client.createSession();
7469

75-
await session.send({
70+
const message = await session.sendAndWait({
7671
prompt: "What is 2+2?",
7772
});
78-
79-
const message = await getFinalAssistantMessage(session);
8073
expect(message?.data.content).toContain("4");
8174

8275
await session.destroy();
@@ -96,12 +89,10 @@ describe("Permission callbacks", async () => {
9689
},
9790
});
9891

99-
await session.send({
92+
await session.sendAndWait({
10093
prompt: "Run 'echo test' and tell me what happens",
10194
});
10295

103-
await getFinalAssistantMessage(session);
104-
10596
expect(permissionRequests.length).toBeGreaterThan(0);
10697

10798
await session.destroy();
@@ -113,8 +104,7 @@ describe("Permission callbacks", async () => {
113104
// Create session without permission handler
114105
const session1 = await client.createSession();
115106
const sessionId = session1.sessionId;
116-
await session1.send({ prompt: "What is 1+1?" });
117-
await getFinalAssistantMessage(session1);
107+
await session1.sendAndWait({ prompt: "What is 1+1?" });
118108

119109
// Resume with permission handler
120110
const session2 = await client.resumeSession(sessionId, {
@@ -124,12 +114,10 @@ describe("Permission callbacks", async () => {
124114
},
125115
});
126116

127-
await session2.send({
117+
await session2.sendAndWait({
128118
prompt: "Run 'echo resumed' for me",
129119
});
130120

131-
await getFinalAssistantMessage(session2);
132-
133121
// Should have permission requests from resumed session
134122
expect(permissionRequests.length).toBeGreaterThan(0);
135123

@@ -143,12 +131,10 @@ describe("Permission callbacks", async () => {
143131
},
144132
});
145133

146-
await session.send({
134+
const message = await session.sendAndWait({
147135
prompt: "Run 'echo test'. If you can't, say 'failed'.",
148136
});
149137

150-
const message = await getFinalAssistantMessage(session);
151-
152138
// Should handle the error and deny permission
153139
expect(message?.data.content?.toLowerCase()).toMatch(/fail|cannot|unable|permission/);
154140

@@ -169,12 +155,10 @@ describe("Permission callbacks", async () => {
169155
},
170156
});
171157

172-
await session.send({
158+
await session.sendAndWait({
173159
prompt: "Run 'echo test'",
174160
});
175161

176-
await getFinalAssistantMessage(session);
177-
178162
expect(receivedToolCallId).toBe(true);
179163

180164
await session.destroy();

0 commit comments

Comments
 (0)