{"record":{"id":"ba28c3f0f9e91552","repo":"BigPizzaV3/CodexPlusPlus","slug":"api-key-canvas-user","errorCode":null,"errorMessage":"请在设置中填写有效 API Key","messagePattern":"请在设置中填写有效 API Key","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/public/canvas.user.js","lineNumber":635,"sourceCode":"  }\n}\n\n  // OpenAI-compatible Chat Completions. Never persist credentials in tree checkpoints.\nfunction 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\nfunction 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\nfunction storedApiConfig(config){\n  const {channel,baseUrl,model,remember,speed,revision}=config;\n  return {channel,baseUrl,model,remember,speed,revision,...remember?{key:config.key}:{}};\n}\n\nfunction apiError(error){\n  if(error?.name==='AbortError')return error;\n  if(error?.canvasApiLocal===true)return error;\n  const code=Number(error?.status??error?.responseStatus);\n  const hint={401:'密钥无效或已过期',403:'接口拒绝访问，请检查权限',404:'地址或模型不存在',408:'接口请求超时',413:'本批资料超过接口大小限制',429:'接口限流或额度不足'}[code];\n  // Provider messages can echo request contents and Authorization; never display them.\n  return Object.assign(Error(hint?`API ${code}：${hint}`:code>=400?`API 请求失败（HTTP ${code}），请检查服务状态`:'API 连接失败，请检查地址、网络及服务状态'),{retryable:!code||code===408||code===429||code>=500});\n}\n","sourceCodeStart":617,"sourceCodeEnd":653,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/public/canvas.user.js#L617-L653","documentation":"For the external API channel, apiConfig() requires a non-empty API key containing no CR/LF characters. A missing or newline-contaminated key would break the Authorization header and leak across lines, so it throws immediately.","triggerScenarios":"apiConfig with channel='external' and input.key empty after trim, or containing \\r or \\n characters.","commonSituations":"User forgot to fill the API Key field; pasted a key with a trailing newline from clipboard; key stored from a multi-line paste; remember=false so the key was not persisted and is empty on next run.","solutions":["Enter a valid API key in the settings panel","Re-copy the key making sure there are no trailing newlines or spaces","Enable 'remember' to persist the key so it is not empty next session","Confirm the provider key is active and not revoked"],"exampleFix":"// before\nconst key = document.querySelector('#key').value; // \"sk-abc\\n\"\n// after\nconst key = document.querySelector('#key').value.replace(/[\\r\\n]/g,'').trim();","handlingStrategy":"validation","validationCode":"const key=(input.key||'').trim();\nif(input.channel==='external' && (!key || /[\\r\\n]/.test(key))) throw new Error('请在设置中填写有效 API Key');","typeGuard":"function hasValidKey(cfg){return typeof cfg?.key==='string' && cfg.key.trim().length>0 && !/[\\r\\n]/.test(cfg.key);}","tryCatchPattern":"try{ cfg=apiConfig(input); }catch(e){ if(e.message.includes('API Key')) showKeyInputError(); }","preventionTips":["Paste keys via a single-line input; strip newlines programmatically","Enable 'remember' to persist the key","Rotate/verify keys in the provider dashboard"],"tags":["validation","api-key","credentials"],"backgroundTag":"missing-api-key","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"}