Skip to content

Commit 65f44e8

Browse files
committed
chore: test tool calls in session tests
1 parent 5198b62 commit 65f44e8

7 files changed

Lines changed: 83 additions & 25 deletions

packages/core/tests/claude-session.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ describe.skipIf(registrySkipReason)("full createSession('claude')", () => {
156156
void vm.respondPermission(sessionId!, request.permissionId, "once");
157157
});
158158

159-
const response = await vm.prompt(
159+
const { response } = await vm.prompt(
160160
sessionId,
161161
`Run ${XU_COMMAND} and tell me what it prints.`,
162162
);
@@ -218,7 +218,7 @@ describe.skipIf(registrySkipReason)("full createSession('claude')", () => {
218218
});
219219
sessionId = session.sessionId;
220220

221-
const response = await promptVm.prompt(
221+
const { response } = await promptVm.prompt(
222222
sessionId,
223223
`Reply with exactly ${TEXT_ONLY_OUTPUT}.`,
224224
);
@@ -290,7 +290,7 @@ describe.skipIf(registrySkipReason)("full createSession('claude')", () => {
290290
);
291291
});
292292

293-
const response = await promptVm.prompt(
293+
const { response } = await promptVm.prompt(
294294
sessionId,
295295
`Run ${NODE_EXECSYNC_COMMAND} and tell me what it prints.`,
296296
);
@@ -367,7 +367,7 @@ describe.skipIf(registrySkipReason)("full createSession('claude')", () => {
367367
);
368368
});
369369

370-
const response = await promptVm.prompt(
370+
const { response } = await promptVm.prompt(
371371
sessionId,
372372
`Run ${NODE_ASYNC_SPAWN_COMMAND} and tell me what it prints.`,
373373
);

packages/core/tests/codex-session.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ describe.skipIf(registrySkipReason)("full createSession('codex')", () => {
204204
void runtime.vm.respondPermission(sessionId, request.permissionId, "once");
205205
});
206206

207-
const response = await runtime.vm.prompt(
207+
const { response } = await runtime.vm.prompt(
208208
sessionId,
209209
`Run ${XU_COMMAND} and tell me what it prints.`,
210210
);
@@ -318,7 +318,7 @@ describe.skipIf(registrySkipReason)("full createSession('codex')", () => {
318318
void runtime.vm.respondPermission(sessionId, request.permissionId, "once");
319319
});
320320

321-
const response = await runtime.vm.prompt(
321+
const { response } = await runtime.vm.prompt(
322322
sessionId,
323323
"Run both xu alpha and xu beta, then summarize the outputs.",
324324
);
@@ -433,7 +433,7 @@ describe.skipIf(registrySkipReason)("full createSession('codex')", () => {
433433
expect(runtime.vm.getSessionModes(sessionId)?.currentModeId).toBe("default");
434434
await runtime.vm.setSessionMode(sessionId, "plan");
435435

436-
const promptResponse = await runtime.vm.prompt(
436+
const { response: promptResponse } = await runtime.vm.prompt(
437437
sessionId,
438438
"Plan the next step without running shell commands.",
439439
);
@@ -529,13 +529,13 @@ describe.skipIf(registrySkipReason)("full createSession('codex')", () => {
529529
});
530530
const sessionId = session.sessionId;
531531

532-
const firstResponse = await runtime.vm.prompt(sessionId, firstPrompt);
532+
const { response: firstResponse } = await runtime.vm.prompt(sessionId, firstPrompt);
533533
expect(firstResponse.error).toBeUndefined();
534534
expect(
535535
(firstResponse.result as { stopReason?: string }).stopReason,
536536
).toBe("end_turn");
537537

538-
const secondResponse = await runtime.vm.prompt(sessionId, secondPrompt);
538+
const { response: secondResponse } = await runtime.vm.prompt(sessionId, secondPrompt);
539539
expect(secondResponse.error).toBeUndefined();
540540
expect(
541541
(secondResponse.result as { stopReason?: string }).stopReason,
@@ -594,7 +594,7 @@ describe.skipIf(registrySkipReason)("full createSession('codex')", () => {
594594
void runtime.vm.respondPermission(sessionId, request.permissionId, "reject");
595595
});
596596

597-
const response = await runtime.vm.prompt(
597+
const { response } = await runtime.vm.prompt(
598598
sessionId,
599599
`Run ${XU_COMMAND} even if permission is denied.`,
600600
);
@@ -654,7 +654,7 @@ describe.skipIf(registrySkipReason)("full createSession('codex')", () => {
654654
const cancelResponse = await runtime.vm.cancelSession(sessionId);
655655
expect(cancelResponse.error).toBeUndefined();
656656

657-
const promptResponse = await promptPromise;
657+
const { response: promptResponse } = await promptPromise;
658658
expect(promptResponse.error).toBeUndefined();
659659
expect(
660660
(promptResponse.result as { stopReason?: string }).stopReason,

packages/core/tests/opencode-session.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ describe.skipIf(registrySkipReason)(
138138
configOptions.some((option) => option.category === "model"),
139139
).toBe(true);
140140

141-
const response = await vm.prompt(
141+
const { response } = await vm.prompt(
142142
sessionId,
143143
"Create notes.txt with the text hello from tool.",
144144
);
@@ -198,7 +198,7 @@ describe.skipIf(registrySkipReason)(
198198
})
199199
).sessionId;
200200

201-
const response = await vm.prompt(
201+
const { response } = await vm.prompt(
202202
sessionId,
203203
"Use bash to write bash-ok into bash-output.txt.",
204204
);
@@ -266,7 +266,7 @@ describe.skipIf(registrySkipReason)(
266266
expect(setModeResponse.error).toBeUndefined();
267267
expect(vm.getSessionModes(sessionId)?.currentModeId).toBe("plan");
268268

269-
const promptResponse = await vm.prompt(
269+
const { response: promptResponse } = await vm.prompt(
270270
sessionId,
271271
"Plan the next step without running tools.",
272272
);
@@ -423,7 +423,7 @@ describe.skipIf(registrySkipReason)(
423423
void vm.respondPermission(sessionId!, request.permissionId, "once");
424424
});
425425

426-
const response = await vm.prompt(
426+
const { response } = await vm.prompt(
427427
sessionId,
428428
"Use bash to write perm-ok into perm-output.txt.",
429429
);
@@ -499,7 +499,7 @@ describe.skipIf(registrySkipReason)(
499499
void vm.respondPermission(sessionId!, request.permissionId, "reject");
500500
});
501501

502-
const response = await vm.prompt(
502+
const { response } = await vm.prompt(
503503
sessionId,
504504
"Use bash to write perm-no into perm-output.txt.",
505505
);
@@ -562,7 +562,7 @@ describe.skipIf(registrySkipReason)(
562562
expect(vm.getSessionModes(sessionId)?.currentModeId).toBe("plan");
563563

564564
const planPrompt = "Plan once and do not run tools.";
565-
const planPromptResponse = await vm.prompt(sessionId, planPrompt);
565+
const { response: planPromptResponse } = await vm.prompt(sessionId, planPrompt);
566566
expect(planPromptResponse.error).toBeUndefined();
567567

568568
const rawBuildResponse = await vm.rawSessionSend(
@@ -576,7 +576,7 @@ describe.skipIf(registrySkipReason)(
576576
expect(vm.getSessionModes(sessionId)?.currentModeId).toBe("build");
577577

578578
const buildPrompt = "Answer normally after returning to build mode.";
579-
const buildPromptResponse = await vm.prompt(sessionId, buildPrompt);
579+
const { response: buildPromptResponse } = await vm.prompt(sessionId, buildPrompt);
580580
expect(buildPromptResponse.error).toBeUndefined();
581581

582582
const modeEvents = vm

packages/core/tests/session-comprehensive.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ describe("comprehensive session API tests", () => {
277277
});
278278

279279
// Prompt with "permission" triggers the mock to emit request/permission
280-
const response = await vm.prompt(sessionId, "test permission flow");
280+
const { response } = await vm.prompt(sessionId, "test permission flow");
281281

282282
expect(response.error).toBeUndefined();
283283
// VM stdout can duplicate lines; check at least 1 permission request arrived
@@ -388,7 +388,7 @@ describe("comprehensive session API tests", () => {
388388
).toBe(true);
389389

390390
// Second session still works
391-
const resp = await vm.prompt(sessionId2, "independent prompt");
391+
const { response: resp } = await vm.prompt(sessionId2, "independent prompt");
392392
expect(resp.error).toBeUndefined();
393393

394394
vm.closeSession(sessionId2);
@@ -595,7 +595,7 @@ describe("comprehensive session API tests", () => {
595595
expect(resumed.sessionId).toBe(sessionId);
596596

597597
// Resumed session is fully functional
598-
const response = await vm.prompt(resumed.sessionId, "after resume");
598+
const { response } = await vm.prompt(resumed.sessionId, "after resume");
599599
expect(response.error).toBeUndefined();
600600

601601
// Throws for unknown sessionId

packages/core/tests/session-lifecycle.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ describe("session lifecycle: resume and destroy", () => {
179179
vm.resumeSession(sessionId);
180180

181181
// Prompt on resumed session works
182-
const response = await vm.prompt(sessionId, "test after resume");
182+
const { response } = await vm.prompt(sessionId, "test after resume");
183183
expect(response.error).toBeUndefined();
184184
const result = response.result as { status: string };
185185
expect(result.status).toBe("complete");

packages/core/tests/session-mock-e2e.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ describe("end-to-end mock agent session with llmock", () => {
246246
});
247247

248248
// Send prompt - triggers multi-turn: tool_use → tool_result → text
249-
const response = await vm.prompt(sessionId, "run ls in the current directory");
249+
const { response } = await vm.prompt(sessionId, "run ls in the current directory");
250250
expect(response.error).toBeUndefined();
251251

252252
// Verify llmock received at least 2 requests (multi-turn)

packages/core/tests/session.test.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ describe("full createSession API", () => {
194194
events.push(event);
195195
});
196196

197-
const response = await vm.prompt(
197+
const { response } = await vm.prompt(
198198
sessionId,
199199
"Reply with exactly the word hello.",
200200
);
@@ -221,7 +221,7 @@ describe("full createSession API", () => {
221221
events.push(event);
222222
});
223223

224-
const response = await vm.prompt(sessionId, "test prompt");
224+
const { response } = await vm.prompt(sessionId, "test prompt");
225225

226226
expect(response.error).toBeUndefined();
227227
expect(response.result).toBeDefined();
@@ -258,6 +258,64 @@ describe("full createSession API", () => {
258258
).rejects.toThrow();
259259
}, 30_000);
260260

261+
test("VM subprocess spawning works alongside a pi session", async () => {
262+
const { sessionId } = await vm.createSession("pi", {
263+
env: {
264+
ANTHROPIC_API_KEY: "mock-key",
265+
ANTHROPIC_BASE_URL: mockUrl,
266+
},
267+
});
268+
269+
// Spawn a node subprocess that writes a file
270+
await vm.writeFile(
271+
"/tmp/write-script.js",
272+
"require('fs').writeFileSync('/tmp/spawn-test.txt', 'spawned-ok')",
273+
);
274+
const proc = vm.kernel.spawn("node", ["/tmp/write-script.js"], {
275+
env: { HOME: "/home/user" },
276+
});
277+
const result = await proc.wait();
278+
279+
// Verify the file was written
280+
const content = await vm.readFile("/tmp/spawn-test.txt");
281+
expect(new TextDecoder().decode(content)).toBe("spawned-ok");
282+
283+
// Session still works after subprocess
284+
const { response } = await vm.prompt(sessionId, "hello");
285+
expect(response.error).toBeUndefined();
286+
287+
vm.closeSession(sessionId);
288+
}, 90_000);
289+
290+
test("VM filesystem operations work alongside a pi session", async () => {
291+
const { sessionId } = await vm.createSession("pi", {
292+
env: {
293+
ANTHROPIC_API_KEY: "mock-key",
294+
ANTHROPIC_BASE_URL: mockUrl,
295+
},
296+
});
297+
298+
// mkdir recursive
299+
await vm.mkdir("/home/user/project/src", { recursive: true });
300+
expect(await vm.exists("/home/user/project/src")).toBe(true);
301+
302+
// Write, read, verify
303+
await vm.writeFile("/home/user/project/src/index.ts", "console.log('hello')");
304+
const content = await vm.readFile("/home/user/project/src/index.ts");
305+
expect(new TextDecoder().decode(content)).toBe("console.log('hello')");
306+
307+
// readdir
308+
const entries = (await vm.readdir("/home/user/project/src"))
309+
.filter((e) => e !== "." && e !== "..");
310+
expect(entries).toContain("index.ts");
311+
312+
// Session still works after filesystem ops
313+
const { response } = await vm.prompt(sessionId, "hello");
314+
expect(response.error).toBeUndefined();
315+
316+
vm.closeSession(sessionId);
317+
}, 90_000);
318+
261319
test("vm.dispose() closes active sessions before kernel", async () => {
262320
await createMockSession(vm);
263321

0 commit comments

Comments
 (0)