fix(pi): preserve exact native model identifiers

This commit is contained in:
SaladDay
2026-08-03 12:56:58 +00:00
parent afd58e31d8
commit 697bcf8e0f
3 changed files with 58 additions and 3 deletions
+28 -1
View File
@@ -100,7 +100,9 @@ pub(crate) fn set_pi_native_default_with_receipt(
provider_key: &str, provider_key: &str,
model_id: &str, model_id: &str,
) -> Result<PiNativeDefaultsReceipt, AppError> { ) -> Result<PiNativeDefaultsReceipt, AppError> {
if provider_key.trim().is_empty() || model_id.trim().is_empty() { // Native model identifiers are opaque exact strings under pinned Pi's
// schema. Do not trim a schema-valid whitespace or edge-whitespace ID.
if provider_key.trim().is_empty() || model_id.is_empty() {
return Err(AppError::InvalidInput( return Err(AppError::InvalidInput(
"Pi default provider and model must be non-empty".to_string(), "Pi default provider and model must be non-empty".to_string(),
)); ));
@@ -201,6 +203,31 @@ mod tests {
use super::*; use super::*;
use serde_json::json; use serde_json::json;
#[test]
#[serial_test::serial]
fn native_default_preserves_a_pinned_exact_whitespace_model_id() {
struct EnvGuard(Option<std::ffi::OsString>);
impl Drop for EnvGuard {
fn drop(&mut self) {
match self.0.take() {
Some(value) => std::env::set_var("CC_SWITCH_TEST_HOME", value),
None => std::env::remove_var("CC_SWITCH_TEST_HOME"),
}
}
}
let temp = tempfile::tempdir().expect("tempdir");
let _home = EnvGuard(std::env::var_os("CC_SWITCH_TEST_HOME"));
std::env::set_var("CC_SWITCH_TEST_HOME", temp.path());
set_pi_native_default_with_receipt("provider", " ")
.expect("pinned exact model id is accepted");
let defaults = read_pi_native_defaults().expect("native defaults");
assert_eq!(defaults.default_provider.as_deref(), Some("provider"));
assert_eq!(defaults.default_model.as_deref(), Some(" "));
assert!(set_pi_native_default_with_receipt("provider", "").is_err());
}
#[test] #[test]
fn default_patch_preserves_every_unowned_field() { fn default_patch_preserves_every_unowned_field() {
let temp = tempfile::tempdir().expect("tempdir"); let temp = tempfile::tempdir().expect("tempdir");
@@ -220,10 +220,13 @@ export function PiProviderForm({
); );
const seen = new Set<string>(); const seen = new Set<string>();
const normalizedModels = models.map((model, index) => { const normalizedModels = models.map((model, index) => {
const id = model.id.trim(); // Pinned Pi treats model IDs as opaque, exact strings. In particular,
// its schema accepts whitespace-only and edge-whitespace IDs; trimming
// here would silently rename an imported model.
const id = model.id;
const modelApi = model.api.trim(); const modelApi = model.api.trim();
const modelBaseUrl = model.baseUrl.trim(); const modelBaseUrl = model.baseUrl.trim();
if (!id) { if (id.length === 0) {
throw new Error(t("pi.form.modelIdRequired", { index: index + 1 })); throw new Error(t("pi.form.modelIdRequired", { index: index + 1 }));
} }
if (seen.has(id)) { if (seen.has(id)) {
+25
View File
@@ -80,6 +80,31 @@ describe("PiProviderForm", () => {
expect(JSON.parse(onSubmit.mock.calls[0][0].settingsConfig)).toEqual(input); expect(JSON.parse(onSubmit.mock.calls[0][0].settingsConfig)).toEqual(input);
}); });
it("preserves pinned exact model IDs instead of trimming them", async () => {
const onSubmit = vi.fn().mockResolvedValue(undefined);
const input = {
name: "Exact IDs",
api: "openai-responses",
baseUrl: "https://api.example.com/v1",
models: [{ id: " " }, { id: " model " }],
};
render(
<PiProviderForm
appId="pi"
providerId="exact-ids"
submitLabel="Save exact IDs"
onSubmit={onSubmit}
onCancel={() => {}}
initialData={{ name: input.name, settingsConfig: input }}
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Save exact IDs" }));
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
expect(JSON.parse(onSubmit.mock.calls[0][0].settingsConfig)).toEqual(input);
});
it("preserves an explicitly false authHeader instead of erasing it", async () => { it("preserves an explicitly false authHeader instead of erasing it", async () => {
const onSubmit = vi.fn().mockResolvedValue(undefined); const onSubmit = vi.fn().mockResolvedValue(undefined);
const input = { const input = {