Skip to content
30 changes: 29 additions & 1 deletion packages/provider-pi/src/server/textGeneration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { makePiTextGeneration } from "./textGeneration.ts";
const decodeJsonLine = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Unknown));
const encodeJsonLine = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));

const makeFakePi = (reply: string) =>
const makeFakePi = (reply: string, setModelError?: string) =>
Effect.gen(function* () {
const stdout = yield* Queue.unbounded<Uint8Array, Cause.Done>();
const received: Array<Record<string, unknown>> = [];
Expand All @@ -35,6 +35,8 @@ const makeFakePi = (reply: string) =>
yield* emit({ type: "agent_settled" });
} else if (record["type"] === "get_last_assistant_text") {
yield* emit({ ...response, success: true, data: { text: reply } });
} else if (record["type"] === "set_model" && setModelError !== undefined) {
yield* emit({ ...response, success: false, error: setModelError });
} else {
yield* emit({ ...response, success: true });
}
Expand Down Expand Up @@ -83,3 +85,29 @@ it.effect("puts linked source control context in the Pi thread title prompt", ()
assert.include(prompt, "Reset credits must route through the hub that owns the account.");
}),
);

it.effect("names the model and the disabled extensions when Pi cannot load it", () =>
Effect.gen(function* () {
const pi = yield* makeFakePi("", "Model not found: demo/demo-model");
const textGeneration = yield* makePiTextGeneration(
{ enabled: true, binaryPath: "pi", launchArgs: "", customModels: [] },
{},
).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, pi.spawner));

const error = yield* textGeneration
.generateCommitMessage({
cwd: process.cwd(),
branch: "main",
stagedSummary: "M README.md",
stagedPatch: "diff --git a/README.md b/README.md",
modelSelection: createModelSelection(ProviderInstanceId.make("pi"), "demo/demo-model"),
})
.pipe(Effect.flip);

assert.equal(error._tag, "TextGenerationError");
assert.include(error.detail, "Pi rejected model 'demo/demo-model'");
assert.include(error.detail, "Model not found: demo/demo-model");
assert.include(error.detail, "--no-extensions");
assert.isUndefined(pi.received.find((record) => record["type"] === "prompt"));
}),
);
23 changes: 18 additions & 5 deletions packages/provider-pi/src/server/textGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,24 @@ export const makePiTextGeneration = Effect.fn("makePiTextGeneration")(function*
detail: `Pi model '${modelSelection.model}' must use provider/model format.`,
});
}
yield* connection.request({
type: "set_model",
provider: parsed.provider,
modelId: parsed.modelId,
});
yield* connection
.request({ type: "set_model", provider: parsed.provider, modelId: parsed.modelId })
.pipe(
// Model discovery loads extensions that this background helper disables.
Effect.mapError((error) =>
error._tag === "PiRpcError" && error.operation === "set_model"
? new TextGenerationError({
operation,
detail:
`Pi rejected model '${modelSelection.model}': ${error.detail ?? "unknown error"}. ` +
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"If the model comes from a Pi extension or package, text generation cannot use it " +
"because it runs Pi with --no-extensions. Pick a built-in Pi model or one defined " +
"in Pi's models.json.",
cause: error,
})
: error,
),
);
}

yield* connection.request({ type: "prompt", message: prompt });
Expand Down
Loading