authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-10-11 00:48:09+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-10 17:48:09-05:00
log62da1cd48bb43def11a79cd6aa033dbc478e4dfd
tree2a7dd3ffaedca36d4cc14bf9d5986ec74ec013af
parent160b18c8667f8370f46708d273d368648316b200
signature Signed by PGP key B5690EEEBB952194

fix(pi): preserve shell command exit codes (#17834)


2 files changed, 65 insertions(+), 2 deletions(-)

packages/provider-pi/src/server/adapter.test.ts+61
...@@ -2821,6 +2821,67 @@ describe("PiAdapterV2", () => {...@@ -2821,6 +2821,67 @@ describe("PiAdapterV2", () => {
2821 ),2821 ),
2822 );2822 );
28232823
2824 it.effect.each([
2825 { toolName: "bash", exitCode: 0, legacy: false },
2826 { toolName: "powershell", exitCode: 2, legacy: false },
2827 { toolName: "bash", exitCode: 1, legacy: true },
2828 { toolName: "powershell", exitCode: 0, legacy: true },
2829 ])(
2830 "normalizes $toolName command results, exit=$exitCode legacy=$legacy",
2831 ({ toolName, exitCode, legacy }) =>
2832 Effect.gen(function* () {
2833 const fake = yield* makeFakePi;
2834 const { runtime, takeEvent } = yield* openRuntime(fake);
2835 const providerThread = yield* runtime.ensureThread({
2836 threadId: THREAD_ID,
2837 modelSelection: modelSelection("default"),
2838 runtimePolicy,
2839 });
2840 yield* startTurn(runtime, providerThread);
2841 yield* fake.takeRequest("prompt");
2842 yield* fake.emit({
2843 type: "tool_execution_start",
2844 toolCallId: "shell-call",
2845 toolName,
2846 args: { command: "echo hello" },
2847 });
2848 const running = yield* takeEvent(
2849 (event) =>
2850 event.type === "turn_item.updated" && event.turnItem.type === "command_execution",
2851 );
2852 assert.isTrue(
2853 running.type === "turn_item.updated" &&
2854 running.turnItem.type === "command_execution" &&
2855 running.turnItem.status === "running" &&
2856 running.turnItem.input === "echo hello",
2857 );
2858 yield* fake.emit({
2859 type: "tool_execution_end",
2860 toolCallId: "shell-call",
2861 toolName,
2862 isError: exitCode !== 0,
2863 result: {
2864 content: [{ type: "text", text: "hello" }],
2865 details: { exitCode: legacy ? exitCode : 99 },
2866 ...(legacy ? {} : { structuredContent: { exit_code: exitCode } }),
2867 },
2868 });
2869 const completed = yield* takeEvent(
2870 (event) =>
2871 event.type === "turn_item.updated" && event.turnItem.type === "command_execution",
2872 );
2873 assert.isTrue(
2874 completed.type === "turn_item.updated" &&
2875 completed.turnItem.type === "command_execution" &&
2876 completed.turnItem.title === toolName &&
2877 completed.turnItem.input === "echo hello" &&
2878 completed.turnItem.output === "hello" &&
2879 completed.turnItem.exitCode === exitCode &&
2880 completed.turnItem.status === (exitCode === 0 ? "completed" : "failed"),
2881 );
2882 }).pipe(Effect.scoped, Effect.provide(layerTest)),
2883 );
2884
2824 it.effect("emits session-start dialogs before a turn exists", () =>2885 it.effect("emits session-start dialogs before a turn exists", () =>
2825 Effect.gen(function* () {2886 Effect.gen(function* () {
2826 const fake = yield* makeFakePi;2887 const fake = yield* makeFakePi;
packages/provider-pi/src/server/adapter.ts+4-2
...@@ -1109,8 +1109,10 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* (...@@ -1109,8 +1109,10 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* (
1109 status,1109 status,
1110 completedAt: completed ? emittedAt : null,1110 completedAt: completed ? emittedAt : null,
1111 } as const;1111 } as const;
1112 if (toolName === "bash") {1112 if (toolName === "bash" || toolName === "powershell") {
1113 const exitCode = recordNumber(recordField(resultRecord, "details"), "exitCode");1113 const exitCode =
1114 recordNumber(recordField(resultRecord, "structuredContent"), "exit_code") ??
1115 recordNumber(recordField(resultRecord, "details"), "exitCode");
1114 yield* emit({1116 yield* emit({
1115 type: "turn_item.updated",1117 type: "turn_item.updated",
1116 driver: PI_PROVIDER,1118 driver: PI_PROVIDER,