{"record":{"id":"06ec5d824f9bd77e","repo":"BigPizzaV3/CodexPlusPlus","slug":"api-06ec5d","errorCode":null,"errorMessage":"API 未能生成本批整理结果","messagePattern":"API 未能生成本批整理结果","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/external-api.mjs","lineNumber":79,"sourceCode":"      size+=part.value.byteLength;if(size>4*1024*1024)throw Object.assign(Error('API 响应超过大小限制'),{canvasApiLocal:true});\n      const text=decoder.decode(part.value,{stream:true});\n      if(!sse){raw+=text;if(/^\\s*(data:|:)/.test(raw)){sse=true;buffer=raw;raw='';}}\n      else buffer+=text;\n      if(sse){let end;while((end=buffer.indexOf('\\n'))>=0){consume(buffer.slice(0,end).replace(/\\r$/,''));buffer=buffer.slice(end+1);}}\n      progress({bytes:size,chars:content.length});if(done)break;\n    }\n    const tail=decoder.decode();if(sse){buffer+=tail;if(buffer.trim())consume(buffer.replace(/\\r$/,''));\n      if(!done&&!finish)throw Object.assign(Error('API 流式连接提前结束，本批未提交'),{canvasApiLocal:true,retryable:true});\n      return {choices:[{finish_reason:finish,message:{content}}]};\n    }\n    try{return JSON.parse(raw+tail);}catch{throw Object.assign(Error('API 返回的不是 JSON，请检查 API 地址'),{canvasApiLocal:true});}\n  }finally{void reader.cancel().catch(()=>{});try{reader.releaseLock();}catch{}}\n}\n\nexport function completionText(body){\n  const choice=body?.choices?.[0];\n  if(choice?.finish_reason==='length')throw Error('API 输出达到长度上限，本批未提交；请使用输出容量更大的模型');\n  if(choice?.finish_reason==='content_filter'||choice?.message?.refusal)throw Error('API 未能生成本批整理结果');\n  const content=choice?.message?.content;\n  const value=typeof content==='string'?content:Array.isArray(content)?content.filter(p=>p?.type==='text').map(p=>p.text||'').join(''):'';\n  if(!value.trim())throw Error('API 未返回有效的 choices[0].message.content，请确认兼容 Chat Completions');\n  return value;\n}\n\nexport function waitForApi(promise,signal){\n  signal?.throwIfAborted();\n  if(!signal)return promise;\n  return new Promise((resolve,reject)=>{\n    const abort=()=>{signal.removeEventListener('abort',abort);reject(signal.reason||new DOMException('Aborted','AbortError'));};\n    signal.addEventListener('abort',abort,{once:true});\n    promise.then(value=>{signal.removeEventListener('abort',abort);resolve(value);},error=>{signal.removeEventListener('abort',abort);reject(error);});\n  });\n}\n\nexport function createApiOrganizer({request,timeoutMs=300000,maxRetries=2,retryDelayMs=2000}){\n  const requests=new Map();","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/external-api.mjs#L61-L97","documentation":"completionText() throws this when the API refused to generate content: choices[0].finish_reason is 'content_filter' or choices[0].message.refusal is set. The provider's safety layer blocked the response, so no batch result exists to merge.","triggerScenarios":"finish_reason==='content_filter' or a truthy message.refusal in the response body; commonly triggered by conversation content the provider's moderation flagged, or an over-restrictive safety system on the provider side.","commonSituations":"Conversations containing flagged content sent to strict providers; a safety system/proxy misfiring on benign technical content; using a provider whose content filter is aggressive (some regional endpoints); system prompt interpreted as policy-violating.","solutions":["Try a different model or provider whose content filter is less restrictive for your content","Review the conversation batch for content that may trip moderation and split it differently","Check message.refusal text in the raw response to understand what was objected to","If a self-hosted/compatible server is behind a moderation proxy, disable or loosen that layer"],"exampleFix":"// before\nconst text = completionText(body); // throws on refusal\n// after\nlet text;\ntry { text = completionText(body); }\ncatch (e) {\n  console.warn('refusal:', body?.choices?.[0]?.message?.refusal);\n  text = await retryWithDifferentModel(messages);\n}","handlingStrategy":"try-catch","validationCode":"const c = body?.choices?.[0];\nif (c?.finish_reason === 'content_filter' || c?.message?.refusal) console.warn('provider refused:', c?.message?.refusal);","typeGuard":null,"tryCatchPattern":"try { text = completionText(body); }\ncatch (e) {\n  if (String(e).includes('未能生成')) {\n    log.warn('refusal:', body?.choices?.[0]?.message?.refusal);\n    return fallbackModelOrNotifyUser();\n  }\n  throw e;\n}","preventionTips":["Log message.refusal verbatim to diagnose what the provider objected to","Test batches against your provider's moderation with representative content","Offer a model/provider switch in the UI for repeated refusals","Avoid embedding the refusal-prone raw conversation verbatim when a summary suffices"],"tags":["content-filter","api-response","moderation"],"backgroundTag":"api-error-response","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"}