mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
fix(pi): preserve exact native model identifiers
This commit is contained in:
@@ -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)) {
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user