{"record":{"id":"ba9751830c86ac85","repo":"BigPizzaV3/CodexPlusPlus","slug":"https-api-external","errorCode":null,"errorMessage":"请输入完整的 HTTPS API 地址","messagePattern":"请输入完整的 HTTPS API 地址","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/external-api.mjs","lineNumber":3,"sourceCode":"// OpenAI-compatible Chat Completions. Never persist credentials in tree checkpoints.\nexport function apiEndpoint(raw){\n  let url;try{url=new URL(String(raw).trim());}catch{throw Error('请输入完整的 HTTPS API 地址');}\n  if(url.protocol!=='https:'||url.username||url.password||url.search||url.hash)throw Error('API 地址须使用 HTTPS，且不含账号、密码、查询参数或片段');\n  const path=url.pathname.replace(/\\/+$/,'');\n  url.pathname=path.endsWith('/chat/completions')?path:(path||'/v1')+'/chat/completions';\n  return url.href;\n}\n\nexport function apiConfig(input){\n  const channel=input?.channel==='external'?'external':'native';\n  const value={channel,baseUrl:String(input?.baseUrl||'').trim(),model:String(input?.model||'').trim(),key:String(input?.key||'').trim(),remember:input?.remember===true,speed:input?.speed==='provider'?'provider':'fast',revision:input?.revision||crypto.randomUUID()};\n  if(channel==='external'){\n    value.endpoint=apiEndpoint(value.baseUrl);\n    if(!value.model||value.model.length>200)throw Error('请填写 API 的模型名称');\n    if(!value.key||/[\\r\\n]/.test(value.key))throw Error('请在设置中填写有效 API Key');\n  }\n  return value;\n}\n\nexport function storedApiConfig(config){","sourceCodeStart":1,"sourceCodeEnd":21,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/external-api.mjs#L1-L21","documentation":"apiEndpoint() validates the user-supplied base URL by constructing a URL object; if String(raw).trim() is empty, relative, or otherwise unparseable by the WHATWG URL parser, it throws this error. It is the first line of validation for external (OpenAI-compatible) channel configuration and happens before any network request.","triggerScenarios":"apiEndpoint called with '' (empty field), 'localhost:8080' without scheme, 'my-api.example.com/v1', 'http//typo', or any string new URL() cannot parse as an absolute URL.","commonSituations":"User leaves the API base URL field blank in the settings form; user types a bare hostname without https://; copy-paste drops the scheme; user pastes a path like /v1/chat/completions alone.","solutions":["Enter a full absolute URL including scheme, e.g. https://api.openai.com/v1","Check for typos in 'https://' (e.g. missing colon or slashes)","Trim surrounding whitespace/newlines from the pasted URL before saving","If a proxy has no public absolute URL, host it under a resolvable https:// address"],"exampleFix":"// before\napiEndpoint('api.example.com/v1')\n// after\napiEndpoint('https://api.example.com/v1')","handlingStrategy":"validation","validationCode":"function validAbsoluteHttpsUrl(raw) { try { const u = new URL(String(raw).trim()); return u.protocol === 'https:' && !u.username && !u.password && !u.search && !u.hash; } catch { return false; } }\nif (!validAbsoluteHttpsUrl(input)) alert('Enter a full https:// URL');","typeGuard":null,"tryCatchPattern":"let endpoint;\ntry { endpoint = apiEndpoint(raw); } catch (e) { showError('API 地址无效：请输入完整 HTTPS 地址'); return; }","preventionTips":["Always paste URLs starting with https://","Trim whitespace before validation","Validate the URL in the form's input handler, before submit","Show a live preview of the normalized endpoint so users see what will be called"],"tags":["url","validation","config"],"backgroundTag":"invalid-url-format","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}