{"record":{"id":"64ecae3d5fe232e2","repo":"BigPizzaV3/CodexPlusPlus","slug":"base-url-64ecae","errorCode":null,"errorMessage":"Base URL 为空","messagePattern":"Base URL 为空","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/provider_import.rs","lineNumber":226,"sourceCode":"        vlm_base_url: String::new(),\n        user_agent: String::new(),\n        sub2api_enabled: false,\n        sub2api_multiplier: String::new(),\n        model_routes: Vec::new(),\n    }\n}\n\nfn normalize_request(mut request: ProviderImportRequest) -> anyhow::Result<ProviderImportRequest> {\n    request.name = request.name.trim().to_string();\n    request.base_url = request.base_url.trim().trim_end_matches('/').to_string();\n    request.api_key = request.api_key.trim().to_string();\n    request.wire_api = request.wire_api.trim().to_ascii_lowercase();\n    request.relay_mode = request.relay_mode.trim().to_ascii_lowercase();\n    if request.name.is_empty() {\n        anyhow::bail!(\"供应商名称为空\");\n    }\n    if request.base_url.is_empty() {\n        anyhow::bail!(\"Base URL 为空\");\n    }\n    if request.api_key.is_empty() {\n        anyhow::bail!(\"API Key 为空\");\n    }\n    request.config_contents = build_config_toml(\n        &request.base_url,\n        &request.api_key,\n        relay_protocol(&request.wire_api),\n    );\n    request.auth_contents = build_auth_json(&request.api_key);\n    Ok(request)\n}\n\nfn relay_protocol(value: &str) -> RelayProtocol {\n    match value.trim().to_ascii_lowercase().as_str() {\n        \"chat\" | \"chat_completions\" | \"chat-completions\" => RelayProtocol::ChatCompletions,\n        _ => RelayProtocol::Responses,\n    }","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/provider_import.rs#L208-L244","documentation":"Thrown by normalize_request in crates/codex-plus-core/src/provider_import.rs when the import request's base_url is empty after trimming and stripping trailing slashes. Note the normalization removes trailing '/' characters first, so a value consisting solely of slashes (\"/\") also becomes empty and triggers this error. The base URL is required to build the provider's config.toml via build_config_toml.","triggerScenarios":"Import request with baseUrl omitted, blank, or \"/\"; share link missing the baseUrl query parameter; pasting only a path fragment (e.g. \"/v1\") instead of a full origin.","commonSituations":"Share links generated without baseUrl; users pasting just the path portion of an endpoint; forms where the URL input was optional but the backend requires it.","solutions":["Supply the full upstream origin, e.g. https://api.vendor.com (scheme + host; path optional)","Check the share link includes the baseUrl parameter and it is URL-encoded correctly","Make sure the value is not just slashes or whitespace"],"exampleFix":"# before\n{\"name\": \"VendorAI\", \"baseUrl\": \"\", \"apiKey\": \"sk-...\"}\n\n# after\n{\"name\": \"VendorAI\", \"baseUrl\": \"https://api.vendor.com\", \"apiKey\": \"sk-...\"}","handlingStrategy":"validation","validationCode":"fn base_url_valid(url: &str) -> bool {\n    let t = url.trim().trim_end_matches('/');\n    !t.is_empty() && (t.starts_with(\\\"http://\\\") || t.starts_with(\\\"https://\\\"))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Require a scheme-qualified origin (https://host) in the import form","Include baseUrl in every generated share link","Validate with a URL parser before submitting the import"],"tags":["provider-import","validation","base-url","required-field"],"backgroundTag":"missing-base-url-config","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}