From 38ba0acdb250116362b6c6d7a77e2b982e279da5 Mon Sep 17 00:00:00 2001 From: wujunchen Date: Sun, 26 Apr 2026 11:09:47 +0800 Subject: [PATCH] fix: parse Claude Code JSON array output and drop --model flag Claude Code `--output-format json` returns a JSON array of event objects, not a single object. Parse the array and extract the result event. Also stop passing --model to let Claude Code use its own default model. Revert timeout to 120s. Change-Id: I5b5d4aa589ff78c0a2806a3e700bac0676a9b354 --- main.js | 7 ++++--- src/cli.ts | 28 ++++++++++++++++++++-------- src/settings.ts | 2 +- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/main.js b/main.js index a18c417..607145d 100644 --- a/main.js +++ b/main.js @@ -7,8 +7,9 @@ {excerpt}`,errorCustomHeadersJsonParse:"Custom headers JSON parse failed: {error}",errorCustomHeadersJsonObject:"Custom headers JSON must be an object",errorCustomHeadersLineFormat:"Custom headers lines must use `Header-Name: value`",errorProviderNonJson:`{label} returned non-JSON: {excerpt}`,errorProviderRequestFailed:"{label} request failed: {error}",errorProviderApiStatus:"{label} API returned HTTP {status}: {excerpt}",noEditor:"Could not find the source note editor",settingUiLanguageName:"UI language",settingUiLanguageDesc:"Controls plugin UI, commands, and notices. Auto follows Obsidian/system language.",settingBackendName:"Backend",settingBackendDesc:"Backend for generating bullets: CLI reuses local login; API supports OpenAI, Anthropic, Gemini, and compatible proxies.",settingCliPathName:"CLI path (optional)",settingCliPathDesc:"Leave blank to auto-detect common paths. Obsidian launched from the GUI may not inherit shell PATH.",settingCliPathPlaceholder:"Example: /Users/you/bin/codex",settingCliTimeoutName:"CLI timeout (ms)",apiProviderHeader:"API Provider",settingProviderPresetName:"Provider preset",settingProviderPresetDesc:"OpenClaw-style provider/model setup: presets define protocol, base URL, and auth defaults.",settingApiFormatName:"API format",settingApiFormatDesc:"Wire protocol for the provider. OpenAI-compatible proxies usually use Chat Completions.",settingBaseUrlName:"Base URL",settingBaseUrlDesc:"Provider root URL, without /chat/completions. Blank uses the preset default.",settingApiKeyName:"API Key",settingApiKeyDesc:"Can be blank for local Ollama/LM Studio; most hosted providers require a key.",settingApiKeyEnvName:"API key env var",settingApiKeyEnvDesc:"Optional. Obsidian GUI may not inherit shell env; direct API Key is usually more reliable.",settingAuthTypeName:"Auth type",settingAuthTypeDesc:"Auto uses provider preset. Custom proxies can use Bearer, x-api-key, or none.",settingHeadersName:"Extra headers",settingHeadersDesc:"Optional. JSON object or one `Header: value` per line, useful for Cloudflare AI Gateway.",settingMaxTokensName:"Max output tokens",settingModelName:"Model",settingModelDescApi:"Model ID for API calls. Supports OpenClaw-style provider/model; matching provider prefixes are stripped.",settingModelDescCli:"Passed as --model for Claude Code. Usually ignored by Codex, which uses its default config.",settingMaxInputName:"Max input characters",settingMaxInputDesc:"Longer notes are truncated before sending to the model. Raise this for long-context models.",promptHeader:"Prompt",settingPromptLanguageName:"Output language",settingPromptLanguageDesc:"Controls title/gist/bullets language. Anchor is always copied verbatim from source.",settingCardRangeName:"Card count range",settingCardRangeDesc:"The model adapts segmentation within this range.",settingCustomPromptName:"Custom system prompt",settingCustomPromptDesc:"Optional. Variables: {minCards}, {maxCards}, {languageInstruction}, {schema}, {example}",settingCustomPromptPlaceholder:"Leave blank to use built-in prompt",settingTestBackendName:"Test current backend",settingTestBackendDescApi:"Sends a minimal LLM request to validate API settings.",settingTestBackendDescCli:"Runs ` --version` to verify the binary can be spawned.",backendTestFailed:"\u2717 Backend test failed: {error}",settingExportFolderName:"Export folder",settingExportFolderDesc:"Parallel-note output location, relative to the Vault root.",cacheHeader:"Cache",settingMaxCacheName:"Max cached notes",settingMaxCacheDesc:"Prunes least-recently accessed note caches above this limit. Cache is stored in plugin cache.json.",cachePruned:"Pruned {count} old cache entries",cachedNotesName:"Cached notes: {count}",cachedNotesDesc:"Cache is invalidated by source SHA1 and generation settings fingerprint.",clearAllCacheButton:"Clear all caches",settingStreamingName:"Streaming output",settingStreamingDesc:"Show real-time LLM output progress during generation (OpenAI Chat and Anthropic formats only)"}};function Ct(r){let n=r?.uiLanguage;return n==="zh"||n==="en"?n:String((typeof navigator<"u"?navigator:null)?.language||"").toLowerCase().startsWith("zh")?"zh":"en"}function b(r,n,e){let t=Ct(r),a=ne[t]||ne.en,i=ne.en[n]||ne.zh[n]||n,s=a[n]||i;return String(s).replace(/\{([a-zA-Z0-9_]+)\}/g,(o,l)=>e&&Object.hasOwn(e,l)?String(e[l]):o)}var ae="record_parallel_reader_cards";function bt(r){let n=[],e=-1,t=0,a=!1,i=!1;for(let s=0;s0&&t--,t===0&&e>=0&&(n.push(r.slice(e,s+1)),e=-1))}return n}function xe(r){let n=(r||"").trim();if(!n)return n;try{return JSON.parse(n),n}catch{}let e=n.match(/```(?:json)?\s*([\s\S]*?)```/);if(e){let a=e[1].trim();try{return JSON.parse(a),a}catch{}}let t=bt(n);t.sort((a,i)=>i.length-a.length);for(let a of t)try{return JSON.parse(a),a}catch{}return n}function S(r,n){let e=xe(r),t;try{t=JSON.parse(e)}catch{throw new Error(b(n||null,"errorLlmNonJson",{excerpt:(r||"").slice(0,500)}))}return G(t)}function G(r){return(r&&Array.isArray(r.cards)?r.cards:[]).filter(e=>!!e&&typeof e=="object").map(e=>({title:typeof e.title=="string"?e.title:"(\u65E0\u6807\u9898)",anchor:typeof e.anchor=="string"?e.anchor:"",gist:typeof e.gist=="string"?e.gist:"",bullets:Array.isArray(e.bullets)?e.bullets.filter(t=>typeof t=="string"):[]}))}function H(r){let n={type:"object",properties:{title:{type:"string"},anchor:{type:"string"},gist:{type:"string"},bullets:{type:"array",items:{type:"string"}}},required:["title","anchor","gist","bullets"]},e={type:"object",properties:{cards:{type:"array",items:n}},required:["cards"]};return r&&(n.additionalProperties=!1,e.additionalProperties=!1),e}function $e(){return{type:"json_schema",json_schema:{name:"parallel_reader_cards",strict:!0,schema:H(!0)}}}function ze(){return{format:{type:"json_schema",name:"parallel_reader_cards",strict:!0,schema:H(!0)}}}function qe(){return{name:ae,description:"Return the generated Parallel Reader summary cards.",input_schema:H(!0)}}function M(r,n){if(n?.trim())return n.trim();let e=ie.default.homedir(),t=process.platform==="win32",a=t?[".cmd",".exe",""]:[""],i=t?[w.default.join(e,"AppData","Roaming","npm"),w.default.join(e,"AppData","Local","Programs","claude-code"),w.default.join(e,"AppData","Local","Programs","codex"),w.default.join(e,".claude","local"),w.default.join(e,".codex","bin"),w.default.join(e,".bun","bin"),w.default.join(e,"scoop","shims"),"C:\\Program Files\\nodejs"]:[w.default.join(e,"bin"),w.default.join(e,".local","bin"),w.default.join(e,".claude","local"),w.default.join(e,".codex","bin"),w.default.join(e,".bun","bin"),w.default.join(e,".npm-global","bin"),w.default.join(e,".cargo","bin"),"/opt/homebrew/bin","/usr/local/bin"];for(let s of i)for(let o of a){let l=w.default.join(s,r+o);try{if(Ye.default.existsSync(l))return l}catch{}}return r}function J(r,n,e,t,a){return new Promise((i,s)=>{let o,l=!1,c=null,d=u=>{l||(l=!0,c&&clearTimeout(c),s(u))},m=u=>{l||(l=!0,c&&clearTimeout(c),i(u))};try{o=(0,We.spawn)(r,n,{stdio:["pipe","pipe","pipe"],env:{...process.env,PATH:[process.env.PATH||"","/usr/local/bin","/opt/homebrew/bin",w.default.join(ie.default.homedir(),".local/bin"),w.default.join(ie.default.homedir(),".claude/local")].filter(Boolean).join(":")}})}catch(u){return s(new Error(`Failed to start ${r}: ${u.message}`))}let p="",f="";if(c=setTimeout(()=>{try{o.kill("SIGKILL")}catch{}d(new Error(`CLI timed out (${t}ms)`))},t),a&&a.onCancel(()=>{try{o.kill("SIGKILL")}catch{}d(new P(a.key))}),o.stdout.on("data",u=>{p+=u.toString("utf8")}),o.stderr.on("data",u=>{f+=u.toString("utf8")}),o.on("error",u=>{d(new Error(`CLI startup error: ${u.message}. Try setting an absolute CLI path.`))}),o.on("close",u=>{if(!l){if(u!==0)return d(new Error(`CLI exited with code ${u} stderr: -${f.slice(0,1e3)}`));m({stdout:p,stderr:f})}}),e)try{o.stdin.write(e),o.stdin.end()}catch{}else try{o.stdin.end()}catch{}})}async function Xe(r,n,e,t){let a=M("claude",e.cliPath),i=["-p","--output-format","json","--append-system-prompt",r,"--disallowed-tools","Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,TodoWrite,Task"];e.model&&i.push("--model",e.model);let{stdout:s}=await J(a,i,n,e.cliTimeoutMs,t),o;try{o=JSON.parse(s)}catch{throw new Error(`claude CLI returned a non-JSON envelope: -`+s.slice(0,500))}let l=o.result||o.content||"";return S(l,e)}async function Ze(r,n,e,t){let a=M("codex",e.cliPath),i=`<> +${f.slice(0,1e3)}`));m({stdout:p,stderr:f})}}),e)try{o.stdin.write(e),o.stdin.end()}catch{}else try{o.stdin.end()}catch{}})}async function Xe(r,n,e,t){let a=M("claude",e.cliPath),i=["-p","--output-format","json","--append-system-prompt",r,"--disallowed-tools","Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,TodoWrite,Task"],{stdout:s}=await J(a,i,n,e.cliTimeoutMs,t),o="";try{let l=JSON.parse(s);if(Array.isArray(l)){let c=l.find(d=>d.type==="result");c&&typeof c.result=="string"&&(o=c.result)}else l&&typeof l=="object"&&(o=l.result||l.content||"")}catch{throw new Error(`claude CLI returned unexpected output: +`+s.slice(0,500))}if(!o)throw new Error(`claude CLI returned no result. Output: +`+s.slice(0,500));return S(o,e)}async function Ze(r,n,e,t){let a=M("codex",e.cliPath),i=`<> ${r} <> ${n} @@ -19,7 +20,7 @@ Output JSON directly with no explanation.`,s=["exec","--skip-git-repo-check","-" `)}function Qe(r){return[r.title,r.gist||"",...(r.bullets||[]).map(n=>"\u2022 "+n)].filter(Boolean).join(` `)}function K(r,n){let e=[`# ${r||"\u5BF9\u7167\u7B14\u8BB0"}`];for(let t of n||[])e.push(Ae(t));return e.join(` -`)}function se(r,n,e){if(!Number.isFinite(n)||n<=0)return-1;let t=Math.floor(n),a=e<0?-1:1;return r<0||r>=t?a<0?t-1:0:Math.min(t-1,Math.max(0,r+a))}function oe(r,n){if(!Array.isArray(r)||n<0||n>=r.length)return-1;let e=Number(r[n]?.startLine);return Number.isFinite(e)&&e>=0?e:-1}var rt=Z(require("crypto"));var Se=1e5,wt=2,$=2,V=100,z={zh:"\u4E2D\u6587",en:"English",auto:"Auto-detect"},Te={auto:"Auto",zh:"\u4E2D\u6587",en:"English"},y={uiLanguage:"auto",backend:"api",cliPath:"",apiProvider:"anthropic",apiFormat:"anthropic-messages",apiBaseUrl:"",apiKey:"",apiKeyEnvVar:"",apiAuthType:"auto",apiHeaders:"",apiMaxTokens:4096,maxDocChars:Se,maxCacheEntries:V,promptLanguage:"zh",minCards:5,maxCards:15,customSystemPrompt:"",model:"claude-sonnet-4-6",exportFolder:"Reading/Articles",cliTimeoutMs:3e5,streaming:!0},T={"anthropic-messages":{label:"Anthropic Messages",defaultBaseUrl:"https://api.anthropic.com/v1",defaultAuthType:"x-api-key"},"openai-chat":{label:"OpenAI Chat Completions",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer",tokenLimitField:"max_tokens"},"openai-responses":{label:"OpenAI Responses",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer"},"google-generative-ai":{label:"Google Gemini generateContent",defaultBaseUrl:"https://generativelanguage.googleapis.com/v1beta",defaultAuthType:"x-goog-api-key"}},Ee={auto:"Auto",bearer:"Authorization: Bearer","x-api-key":"x-api-key","x-goog-api-key":"x-goog-api-key","api-key":"api-key",none:"None"},k={anthropic:{label:"Anthropic",format:"anthropic-messages",baseUrl:"https://api.anthropic.com/v1",authType:"x-api-key",envVar:"ANTHROPIC_API_KEY",model:"claude-sonnet-4-6"},openai:{label:"OpenAI",format:"openai-chat",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",tokenLimitField:"max_completion_tokens",model:"gpt-5.1"},"openai-responses":{label:"OpenAI Responses",format:"openai-responses",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",modelPrefix:"openai",model:"gpt-5.1"},google:{label:"Google Gemini",format:"google-generative-ai",baseUrl:"https://generativelanguage.googleapis.com/v1beta",authType:"x-goog-api-key",envVar:"GEMINI_API_KEY",model:"gemini-3-pro-preview"},openrouter:{label:"OpenRouter",format:"openai-chat",baseUrl:"https://openrouter.ai/api/v1",authType:"bearer",envVar:"OPENROUTER_API_KEY",model:""},groq:{label:"Groq",format:"openai-chat",baseUrl:"https://api.groq.com/openai/v1",authType:"bearer",envVar:"GROQ_API_KEY",model:""},deepseek:{label:"DeepSeek",format:"openai-chat",baseUrl:"https://api.deepseek.com",authType:"bearer",envVar:"DEEPSEEK_API_KEY",model:"deepseek-chat"},moonshot:{label:"Moonshot / Kimi",format:"openai-chat",baseUrl:"https://api.moonshot.ai/v1",authType:"bearer",envVar:"MOONSHOT_API_KEY",model:"kimi-k2.5"},qianfan:{label:"Baidu Qianfan",format:"openai-chat",baseUrl:"https://qianfan.baidubce.com/v2",authType:"bearer",envVar:"QIANFAN_API_KEY",model:"deepseek-v3.2"},minimax:{label:"MiniMax (Anthropic-compatible)",format:"anthropic-messages",baseUrl:"https://api.minimax.io/anthropic",authType:"bearer",envVar:"MINIMAX_API_KEY",model:"MiniMax-M2.1"},xai:{label:"xAI",format:"openai-chat",baseUrl:"https://api.x.ai/v1",authType:"bearer",envVar:"XAI_API_KEY",model:""},mistral:{label:"Mistral",format:"openai-chat",baseUrl:"https://api.mistral.ai/v1",authType:"bearer",envVar:"MISTRAL_API_KEY",model:""},cerebras:{label:"Cerebras",format:"openai-chat",baseUrl:"https://api.cerebras.ai/v1",authType:"bearer",envVar:"CEREBRAS_API_KEY",model:""},zai:{label:"Z.AI / GLM",format:"openai-chat",baseUrl:"https://api.z.ai/api/paas/v4",authType:"bearer",envVar:"ZAI_API_KEY",model:""},ollama:{label:"Ollama (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:11434/v1",authType:"none",envVar:"",model:""},lmstudio:{label:"LM Studio (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:1234/v1",authType:"none",envVar:"",model:""},"custom-openai":{label:"Custom OpenAI-compatible",format:"openai-chat",baseUrl:"",authType:"bearer",envVar:"",model:""},"custom-anthropic":{label:"Custom Anthropic-compatible",format:"anthropic-messages",baseUrl:"",authType:"x-api-key",envVar:"",model:""}};function _(r){return rt.default.createHash("sha1").update(r,"utf8").digest("hex")}function Pe(r){return Array.isArray(r)?"["+r.map(Pe).join(",")+"]":r&&typeof r=="object"?"{"+Object.keys(r).sort().map(n=>JSON.stringify(n)+":"+Pe(r[n])).join(",")+"}":JSON.stringify(r)}function Fe(r){return r==="api"||r==="anthropic-api"}function v(r){return k[r.apiProvider]||k.anthropic}function A(r){let n=v(r),e=(r.apiFormat||n.format||"").trim();return T[e]?e:n.format}function F(r){let n=A(r),e=v(r),t=(r.apiBaseUrl||"").trim();if(t)return t.replace(/\/+$/,"");if((r.apiProvider||"").startsWith("custom-"))throw new Error(b(r,"errorCustomProviderBaseUrlRequired"));let a=(e.baseUrl||T[n].defaultBaseUrl||"").trim();if(!a)throw new Error(b(r,"errorApiBaseUrlMissing"));return a.replace(/\/+$/,"")}function ke(r){let n=(r.apiAuthType||"auto").trim();if(n&&n!=="auto")return n;let e=v(r),t=A(r);return e.authType||T[t].defaultAuthType||"bearer"}function nt(r){let n=(r.apiKey||"").trim();if(n)return n;let e=(r.apiKeyEnvVar||v(r).envVar||"").trim();return e&&process.env?.[e]?String(process.env[e]).trim():""}function I(r){let n=(r.model||"").trim();if(!n)throw new Error(b(r,"errorModelMissing"));let e=v(r),t=[r.apiProvider,e.modelPrefix].map(i=>(i||"").trim()).filter(Boolean),a=n.toLowerCase();for(let i of t){let s=i.toLowerCase();if(a.startsWith(s+"/"))return n.slice(i.length+1).trim()}return n}function Ne(r,n){let e=v(r),t=k[n]||k.anthropic,a=(r.model||"").trim(),i=!a||a===y.model||!!e.model&&a===e.model;r.apiProvider=n,r.apiFormat=t.format,r.apiBaseUrl=t.baseUrl,r.apiAuthType=t.authType||"auto",r.apiKeyEnvVar=t.envVar||"",i&&(r.model=t.model||"")}function De(r){Te[r.uiLanguage]||(r.uiLanguage=y.uiLanguage),(!r.apiProvider||!k[r.apiProvider])&&(r.apiProvider="anthropic");let n=v(r);(!r.apiFormat||!T[r.apiFormat])&&(r.apiFormat=n.format),(!r.apiAuthType||!Ee[r.apiAuthType])&&(r.apiAuthType="auto"),r.backend==="anthropic-api"&&(r.apiProvider=r.apiProvider||"anthropic",r.apiFormat=r.apiFormat||"anthropic-messages",r.apiBaseUrl=r.apiBaseUrl||k.anthropic.baseUrl,r.apiAuthType=r.apiAuthType||"x-api-key",r.apiKeyEnvVar=r.apiKeyEnvVar||"ANTHROPIC_API_KEY");let e=Number(r.apiMaxTokens);(!Number.isFinite(e)||e<=0)&&(r.apiMaxTokens=y.apiMaxTokens);let t=Number(r.maxDocChars);return(!Number.isFinite(t)||t<1e3)&&(r.maxDocChars=y.maxDocChars),r.maxCacheEntries=at(r.maxCacheEntries),z[r.promptLanguage]||(r.promptLanguage=y.promptLanguage),r.minCards=et(r.minCards,y.minCards),r.maxCards=et(r.maxCards,y.maxCards),r.maxCardstt(s)-tt(l)||i.localeCompare(o)).slice(0,t.length-e).map(([i])=>i);for(let i of a)delete r[i];return a}function le(r){let n=De(Object.assign({},y,r||{})),e=Fe(n.backend),t=v(n),a=A(n),i=e?(n.apiBaseUrl||t.baseUrl||T[a]?.defaultBaseUrl||"").trim().replace(/\/+$/,""):"";return _(Pe({cacheSchemaVersion:$,promptVersion:wt,maxDocChars:Number(n.maxDocChars)||y.maxDocChars,promptLanguage:n.promptLanguage,minCards:n.minCards,maxCards:n.maxCards,customSystemPromptHash:_(n.customSystemPrompt||""),backend:n.backend,model:n.model,apiProvider:e?n.apiProvider:"",apiFormat:e?a:"",apiBaseUrl:i,apiAuthType:e?ke(n):"",apiHeadersHash:e?_(n.apiHeaders||""):"",apiMaxTokens:e?Number(n.apiMaxTokens)||y.apiMaxTokens:0,structuredOutputVersion:1}))}function ce(r,n,e){return!!r&&r.schemaVersion===$&&r.contentHash===_(n)&&r.settingsHash===le(e)}function vt(r){return r==="en"?"Write title, gist, and bullets in English.":r==="auto"?"Write title, gist, and bullets in the main language of the source document.":"\u7528\u4E2D\u6587\u8F93\u51FA title\u3001gist \u548C bullets\u3002"}function xt(r){return r==="en"?`{"cards":[ +`)}function se(r,n,e){if(!Number.isFinite(n)||n<=0)return-1;let t=Math.floor(n),a=e<0?-1:1;return r<0||r>=t?a<0?t-1:0:Math.min(t-1,Math.max(0,r+a))}function oe(r,n){if(!Array.isArray(r)||n<0||n>=r.length)return-1;let e=Number(r[n]?.startLine);return Number.isFinite(e)&&e>=0?e:-1}var rt=Z(require("crypto"));var Se=1e5,wt=2,$=2,V=100,z={zh:"\u4E2D\u6587",en:"English",auto:"Auto-detect"},Te={auto:"Auto",zh:"\u4E2D\u6587",en:"English"},y={uiLanguage:"auto",backend:"api",cliPath:"",apiProvider:"anthropic",apiFormat:"anthropic-messages",apiBaseUrl:"",apiKey:"",apiKeyEnvVar:"",apiAuthType:"auto",apiHeaders:"",apiMaxTokens:4096,maxDocChars:Se,maxCacheEntries:V,promptLanguage:"zh",minCards:5,maxCards:15,customSystemPrompt:"",model:"claude-sonnet-4-6",exportFolder:"Reading/Articles",cliTimeoutMs:12e4,streaming:!0},T={"anthropic-messages":{label:"Anthropic Messages",defaultBaseUrl:"https://api.anthropic.com/v1",defaultAuthType:"x-api-key"},"openai-chat":{label:"OpenAI Chat Completions",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer",tokenLimitField:"max_tokens"},"openai-responses":{label:"OpenAI Responses",defaultBaseUrl:"https://api.openai.com/v1",defaultAuthType:"bearer"},"google-generative-ai":{label:"Google Gemini generateContent",defaultBaseUrl:"https://generativelanguage.googleapis.com/v1beta",defaultAuthType:"x-goog-api-key"}},Ee={auto:"Auto",bearer:"Authorization: Bearer","x-api-key":"x-api-key","x-goog-api-key":"x-goog-api-key","api-key":"api-key",none:"None"},k={anthropic:{label:"Anthropic",format:"anthropic-messages",baseUrl:"https://api.anthropic.com/v1",authType:"x-api-key",envVar:"ANTHROPIC_API_KEY",model:"claude-sonnet-4-6"},openai:{label:"OpenAI",format:"openai-chat",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",tokenLimitField:"max_completion_tokens",model:"gpt-5.1"},"openai-responses":{label:"OpenAI Responses",format:"openai-responses",baseUrl:"https://api.openai.com/v1",authType:"bearer",envVar:"OPENAI_API_KEY",modelPrefix:"openai",model:"gpt-5.1"},google:{label:"Google Gemini",format:"google-generative-ai",baseUrl:"https://generativelanguage.googleapis.com/v1beta",authType:"x-goog-api-key",envVar:"GEMINI_API_KEY",model:"gemini-3-pro-preview"},openrouter:{label:"OpenRouter",format:"openai-chat",baseUrl:"https://openrouter.ai/api/v1",authType:"bearer",envVar:"OPENROUTER_API_KEY",model:""},groq:{label:"Groq",format:"openai-chat",baseUrl:"https://api.groq.com/openai/v1",authType:"bearer",envVar:"GROQ_API_KEY",model:""},deepseek:{label:"DeepSeek",format:"openai-chat",baseUrl:"https://api.deepseek.com",authType:"bearer",envVar:"DEEPSEEK_API_KEY",model:"deepseek-chat"},moonshot:{label:"Moonshot / Kimi",format:"openai-chat",baseUrl:"https://api.moonshot.ai/v1",authType:"bearer",envVar:"MOONSHOT_API_KEY",model:"kimi-k2.5"},qianfan:{label:"Baidu Qianfan",format:"openai-chat",baseUrl:"https://qianfan.baidubce.com/v2",authType:"bearer",envVar:"QIANFAN_API_KEY",model:"deepseek-v3.2"},minimax:{label:"MiniMax (Anthropic-compatible)",format:"anthropic-messages",baseUrl:"https://api.minimax.io/anthropic",authType:"bearer",envVar:"MINIMAX_API_KEY",model:"MiniMax-M2.1"},xai:{label:"xAI",format:"openai-chat",baseUrl:"https://api.x.ai/v1",authType:"bearer",envVar:"XAI_API_KEY",model:""},mistral:{label:"Mistral",format:"openai-chat",baseUrl:"https://api.mistral.ai/v1",authType:"bearer",envVar:"MISTRAL_API_KEY",model:""},cerebras:{label:"Cerebras",format:"openai-chat",baseUrl:"https://api.cerebras.ai/v1",authType:"bearer",envVar:"CEREBRAS_API_KEY",model:""},zai:{label:"Z.AI / GLM",format:"openai-chat",baseUrl:"https://api.z.ai/api/paas/v4",authType:"bearer",envVar:"ZAI_API_KEY",model:""},ollama:{label:"Ollama (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:11434/v1",authType:"none",envVar:"",model:""},lmstudio:{label:"LM Studio (local)",format:"openai-chat",baseUrl:"http://127.0.0.1:1234/v1",authType:"none",envVar:"",model:""},"custom-openai":{label:"Custom OpenAI-compatible",format:"openai-chat",baseUrl:"",authType:"bearer",envVar:"",model:""},"custom-anthropic":{label:"Custom Anthropic-compatible",format:"anthropic-messages",baseUrl:"",authType:"x-api-key",envVar:"",model:""}};function _(r){return rt.default.createHash("sha1").update(r,"utf8").digest("hex")}function Pe(r){return Array.isArray(r)?"["+r.map(Pe).join(",")+"]":r&&typeof r=="object"?"{"+Object.keys(r).sort().map(n=>JSON.stringify(n)+":"+Pe(r[n])).join(",")+"}":JSON.stringify(r)}function Fe(r){return r==="api"||r==="anthropic-api"}function v(r){return k[r.apiProvider]||k.anthropic}function A(r){let n=v(r),e=(r.apiFormat||n.format||"").trim();return T[e]?e:n.format}function F(r){let n=A(r),e=v(r),t=(r.apiBaseUrl||"").trim();if(t)return t.replace(/\/+$/,"");if((r.apiProvider||"").startsWith("custom-"))throw new Error(b(r,"errorCustomProviderBaseUrlRequired"));let a=(e.baseUrl||T[n].defaultBaseUrl||"").trim();if(!a)throw new Error(b(r,"errorApiBaseUrlMissing"));return a.replace(/\/+$/,"")}function ke(r){let n=(r.apiAuthType||"auto").trim();if(n&&n!=="auto")return n;let e=v(r),t=A(r);return e.authType||T[t].defaultAuthType||"bearer"}function nt(r){let n=(r.apiKey||"").trim();if(n)return n;let e=(r.apiKeyEnvVar||v(r).envVar||"").trim();return e&&process.env?.[e]?String(process.env[e]).trim():""}function I(r){let n=(r.model||"").trim();if(!n)throw new Error(b(r,"errorModelMissing"));let e=v(r),t=[r.apiProvider,e.modelPrefix].map(i=>(i||"").trim()).filter(Boolean),a=n.toLowerCase();for(let i of t){let s=i.toLowerCase();if(a.startsWith(s+"/"))return n.slice(i.length+1).trim()}return n}function Ne(r,n){let e=v(r),t=k[n]||k.anthropic,a=(r.model||"").trim(),i=!a||a===y.model||!!e.model&&a===e.model;r.apiProvider=n,r.apiFormat=t.format,r.apiBaseUrl=t.baseUrl,r.apiAuthType=t.authType||"auto",r.apiKeyEnvVar=t.envVar||"",i&&(r.model=t.model||"")}function De(r){Te[r.uiLanguage]||(r.uiLanguage=y.uiLanguage),(!r.apiProvider||!k[r.apiProvider])&&(r.apiProvider="anthropic");let n=v(r);(!r.apiFormat||!T[r.apiFormat])&&(r.apiFormat=n.format),(!r.apiAuthType||!Ee[r.apiAuthType])&&(r.apiAuthType="auto"),r.backend==="anthropic-api"&&(r.apiProvider=r.apiProvider||"anthropic",r.apiFormat=r.apiFormat||"anthropic-messages",r.apiBaseUrl=r.apiBaseUrl||k.anthropic.baseUrl,r.apiAuthType=r.apiAuthType||"x-api-key",r.apiKeyEnvVar=r.apiKeyEnvVar||"ANTHROPIC_API_KEY");let e=Number(r.apiMaxTokens);(!Number.isFinite(e)||e<=0)&&(r.apiMaxTokens=y.apiMaxTokens);let t=Number(r.maxDocChars);return(!Number.isFinite(t)||t<1e3)&&(r.maxDocChars=y.maxDocChars),r.maxCacheEntries=at(r.maxCacheEntries),z[r.promptLanguage]||(r.promptLanguage=y.promptLanguage),r.minCards=et(r.minCards,y.minCards),r.maxCards=et(r.maxCards,y.maxCards),r.maxCardstt(s)-tt(l)||i.localeCompare(o)).slice(0,t.length-e).map(([i])=>i);for(let i of a)delete r[i];return a}function le(r){let n=De(Object.assign({},y,r||{})),e=Fe(n.backend),t=v(n),a=A(n),i=e?(n.apiBaseUrl||t.baseUrl||T[a]?.defaultBaseUrl||"").trim().replace(/\/+$/,""):"";return _(Pe({cacheSchemaVersion:$,promptVersion:wt,maxDocChars:Number(n.maxDocChars)||y.maxDocChars,promptLanguage:n.promptLanguage,minCards:n.minCards,maxCards:n.maxCards,customSystemPromptHash:_(n.customSystemPrompt||""),backend:n.backend,model:n.model,apiProvider:e?n.apiProvider:"",apiFormat:e?a:"",apiBaseUrl:i,apiAuthType:e?ke(n):"",apiHeadersHash:e?_(n.apiHeaders||""):"",apiMaxTokens:e?Number(n.apiMaxTokens)||y.apiMaxTokens:0,structuredOutputVersion:1}))}function ce(r,n,e){return!!r&&r.schemaVersion===$&&r.contentHash===_(n)&&r.settingsHash===le(e)}function vt(r){return r==="en"?"Write title, gist, and bullets in English.":r==="auto"?"Write title, gist, and bullets in the main language of the source document.":"\u7528\u4E2D\u6587\u8F93\u51FA title\u3001gist \u548C bullets\u3002"}function xt(r){return r==="en"?`{"cards":[ {"title":"U-shaped gains","anchor":"Who benefits from AI? Overall, it shifts the score from one to seven","gist":"AI productivity gains form a U shape, with both ends benefiting most","bullets":["Top-paid software managers benefit by accelerating existing work","Low-paid workers use AI to create new side income","Middle-layer specialists gain less because prompt precision is hard to trust","Average reported benefit is 5.1/7, with 42% describing gains as unclear"]} ]}`:`{"cards":[ {"title":"U \u578B\u6536\u76CA\u66F2\u7EBF","anchor":"\u90A3\u8C01\u53C8\u4F1A\u88AB AI \u6240\u53D7\u76CA\uFF1F\u6574\u4F53\u6765\u770B\uFF0C\u5B83\u628A\u6574\u4E2A\u5206\u6570\u53D8\u6210\u4E86\u4E00\u5206\u5230\u4E03\u5206","gist":"AI \u751F\u4EA7\u529B\u6536\u76CA\u5448 U \u578B\uFF0C\u4E24\u7AEF\u53D7\u76CA\u6700\u5927\u3001\u4E2D\u95F4\u5C42\u584C\u9677","bullets":["\u6700\u9AD8\u85AA\u5C97\u4F4D\uFF08\u8F6F\u4EF6\u7BA1\u7406\uFF09\u901A\u8FC7\u52A0\u901F\u65E2\u6709\u5DE5\u4F5C\u53D7\u76CA\u6700\u5927","\u6700\u4F4E\u85AA\u5C97\u4F4D\uFF08\u5916\u5356\u5458\u3001\u56ED\u827A\u5DE5\uFF09\u7528 AI \u5F00\u526F\u4E1A\u521B\u9020\u65B0\u6536\u5165","\u4E2D\u95F4\u5C42\u79D1\u5B66\u5BB6\u3001\u5F8B\u5E08\u6536\u76CA\u6700\u5C11\uFF0C\u90E8\u5206\u56E0\u5BF9 prompt \u7CBE\u5EA6\u4FE1\u4EFB\u4E0D\u8DB3","\u5168\u4F53\u5747\u5206 5.1/7\uFF0C42% \u62A5\u544A\u6536\u76CA\u6A21\u7CCA"]} diff --git a/src/cli.ts b/src/cli.ts index 7f37ff3..b33677a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -165,18 +165,30 @@ export async function summarizeViaClaudeCode( '--disallowed-tools', 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,TodoWrite,Task', ]; - if (settings.model) { - args.push('--model', settings.model); - } const { stdout } = await runCli(cmd, args, user, settings.cliTimeoutMs, job); - let envelope; + // --output-format json produces a JSON array of event objects. + // Find the "result" entry and extract its .result text field. + let resultText = ''; try { - envelope = JSON.parse(stdout); - } catch (_e) { - throw new Error('claude CLI returned a non-JSON envelope:\n' + stdout.slice(0, 500)); + const events = JSON.parse(stdout); + if (Array.isArray(events)) { + const resultEvent = events.find((e: Record) => e.type === 'result'); + if (resultEvent && typeof resultEvent.result === 'string') { + resultText = resultEvent.result; + } + } else if (events && typeof events === 'object') { + // Older CLI versions return a single object + resultText = events.result || events.content || ''; + } + } catch (_) { + throw new Error('claude CLI returned unexpected output:\n' + stdout.slice(0, 500)); } - const resultText = envelope.result || envelope.content || ''; + + if (!resultText) { + throw new Error('claude CLI returned no result. Output:\n' + stdout.slice(0, 500)); + } + return parseCardsJson(resultText, settings); } diff --git a/src/settings.ts b/src/settings.ts index cd2c599..88c8b15 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -39,7 +39,7 @@ export const DEFAULT_SETTINGS: PluginSettings = { customSystemPrompt: '', model: 'claude-sonnet-4-6', exportFolder: 'Reading/Articles', - cliTimeoutMs: 300000, + cliTimeoutMs: 120000, streaming: true, };