@ian2018cs/agenthub 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -68,7 +68,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
68
68
|
Path: \`${E.path}\``,timestamp:Date.now()}]),E.exists&&l&&l(E.path));break;case"config":x&&x();break;default:console.warn("Unknown built-in command action:",v)}},[l,x,ht]),hr=s.useRef(null),na=s.useCallback(async(i,v)=>{const{content:E,hasBashCommands:j,hasFileIncludes:D}=i;if(j&&!window.confirm("此命令包含将被执行的 bash 命令。是否继续?")){X(de=>[...de,{type:"assistant",content:"❌ 命令执行已取消",timestamp:Date.now()}]);return}Z(E),setTimeout(()=>{if(hr.current){const G={preventDefault:()=>{}};hr.current(G)}},50)},[]),Gt=s.useCallback(async i=>{if(!(!i||!t))try{const v=F.match(new RegExp(`${i.name}\\s*(.*)`)),E=v&&v[1]?v[1].trim().split(/\s+/):[],j={projectPath:t.path,projectName:t.name,sessionId:M,provider:mt,model:ht,tokenUsage:Gr},D=await ie("/api/commands/execute",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({commandName:i.name,commandPath:i.path,args:E,context:j})});if(!D.ok)throw new Error("Failed to execute command");const G=await D.json();G.type==="builtin"?aa(G):G.type==="custom"&&await na(G,E),Z(""),lt(!1),gt(-1),ct(""),dt(-1)}catch(v){console.error("Error executing command:",v),X(E=>[...E,{type:"assistant",content:`Error executing command: ${v.message}`,timestamp:Date.now()}])}},[F,t,M,mt,ht,Gr]),oa=s.useMemo(()=>{const i=new Map;return(v,E)=>{const j=`${v.length}-${E.length}-${v.slice(0,50)}`;if(i.has(j))return i.get(j);const D=ia(v,E);if(i.set(j,D),i.size>100){const G=i.keys().next().value;i.delete(G)}return D}},[]),Ot=s.useCallback(async(i,v,E=!1,j="claude")=>{if(!i||!v)return[];const D=!E;D?Ne(!0):be(!0);try{const G=E?Re:0,de=await ve.sessionMessages(i,v,Ie,G,j);if(!de.ok)throw new Error("Failed to load session messages");const H=await de.json();if(D&&H.tokenUsage&&Et(H.tokenUsage),H.hasMore!==void 0)return je(H.hasMore),ye(H.total),pe(G+(H.messages?.length||0)),H.messages||[];{const ee=H.messages||[];return je(!1),ye(ee.length),ee}}catch(G){return console.error("Error loading session messages:",G),[]}finally{D?Ne(!1):be(!1)}},[Re]),ia=(i,v)=>{const E=i.split(`
|
|
69
69
|
`),j=v.split(`
|
|
70
70
|
`),D=[];let G=0,de=0;for(;G<E.length||de<j.length;){const H=E[G],ee=j[de];G>=E.length?(D.push({type:"added",content:ee,lineNum:de+1}),de++):de>=j.length?(D.push({type:"removed",content:H,lineNum:G+1}),G++):H===ee?(G++,de++):(D.push({type:"removed",content:H,lineNum:G+1}),D.push({type:"added",content:ee,lineNum:de+1}),G++,de++)}return D},la=i=>{const v=[],E=new Map;for(const j of i)if(j.message?.role==="user"&&Array.isArray(j.message?.content))for(const D of j.message.content)D.type==="tool_result"&&E.set(D.tool_use_id,{content:D.content,isError:D.is_error,timestamp:new Date(j.timestamp||Date.now()),toolUseResult:j.toolUseResult||null});for(const j of i)if(!j.isMeta){if(j.message?.role==="user"&&j.message?.content){let D="",G="user";if(Array.isArray(j.message.content)){const ee=[];for(const fe of j.message.content)fe.type==="text"&&ee.push(Mt(fe.text));D=ee.join(`
|
|
71
|
-
`)}else typeof j.message.content=="string"?D=Mt(j.message.content):D=Mt(String(j.message.content));const de=D.match(/<command-name>(\/[^<]+)<\/command-name>/);de&&(D=de[1]),!D||D.startsWith("<command-args>")||D.startsWith("<local-command-stdout>")||D.startsWith("<system-reminder>")||D.startsWith("Caveat:")||D.startsWith("This session is being continued from a previous")||D.startsWith("[Request interrupted")||(D=tr(D),v.push({type:G,content:D,timestamp:j.timestamp||new Date().toISOString()}))}else if(j.type==="thinking"&&j.message?.content)v.push({type:"assistant",content:tr(j.message.content),timestamp:j.timestamp||new Date().toISOString(),isThinking:!0});else if(j.type==="tool_use"&&j.toolName)v.push({type:"assistant",content:"",timestamp:j.timestamp||new Date().toISOString(),isToolUse:!0,toolName:j.toolName,toolInput:j.toolInput||"",toolCallId:j.toolCallId});else if(j.type==="tool_result"){for(let D=v.length-1;D>=0;D--)if(v[D].isToolUse&&!v[D].toolResult&&(!j.toolCallId||v[D].toolCallId===j.toolCallId)){v[D].toolResult={content:j.output||"",isError:!1};break}}else if(j.message?.role==="assistant"&&j.message?.content){if(Array.isArray(j.message.content)){for(const D of j.message.content)if(D.type==="text"){let G=D.text;typeof G=="string"&&(G=tr(G)),v.push({type:"assistant",content:G,timestamp:j.timestamp||new Date().toISOString()})}else if(D.type==="tool_use"){const G=E.get(D.id);v.push({type:"assistant",content:"",timestamp:j.timestamp||new Date().toISOString(),isToolUse:!0,toolName:D.name,toolInput:JSON.stringify(D.input),toolResult:G?{content:typeof G.content=="string"?G.content:JSON.stringify(G.content),isError:G.isError,toolUseResult:G.toolUseResult}:null,toolError:G?.isError||!1,toolResultTimestamp:G?.timestamp||new Date})}}else if(typeof j.message.content=="string"){let D=j.message.content;D=tr(D),v.push({type:"assistant",content:D,timestamp:j.timestamp||new Date().toISOString()})}}}return v},Zr=s.useMemo(()=>la(ne),[ne]),ut=s.useCallback(()=>{we.current&&(we.current.scrollTop=we.current.scrollHeight)},[]),Yt=s.useCallback(()=>{if(!we.current)return!1;const{scrollTop:i,scrollHeight:v,clientHeight:E}=we.current;return v-i-E<50},[]),Xr=s.useCallback(async i=>{if(!i||J.current||Ee||!Se||!r||!t)return!1;J.current=!0;const v=i.scrollHeight,E=i.scrollTop;try{const j=await Ot(t.name,r.id,!0,"claude");return j.length>0
|
|
71
|
+
`)}else typeof j.message.content=="string"?D=Mt(j.message.content):D=Mt(String(j.message.content));const de=D.match(/<command-name>(\/[^<]+)<\/command-name>/);de&&(D=de[1]),!D||D.startsWith("<command-args>")||D.startsWith("<local-command-stdout>")||D.startsWith("<system-reminder>")||D.startsWith("Caveat:")||D.startsWith("This session is being continued from a previous")||D.startsWith("[Request interrupted")||(D=tr(D),v.push({type:G,content:D,timestamp:j.timestamp||new Date().toISOString()}))}else if(j.type==="thinking"&&j.message?.content)v.push({type:"assistant",content:tr(j.message.content),timestamp:j.timestamp||new Date().toISOString(),isThinking:!0});else if(j.type==="tool_use"&&j.toolName)v.push({type:"assistant",content:"",timestamp:j.timestamp||new Date().toISOString(),isToolUse:!0,toolName:j.toolName,toolInput:j.toolInput||"",toolCallId:j.toolCallId});else if(j.type==="tool_result"){for(let D=v.length-1;D>=0;D--)if(v[D].isToolUse&&!v[D].toolResult&&(!j.toolCallId||v[D].toolCallId===j.toolCallId)){v[D].toolResult={content:j.output||"",isError:!1};break}}else if(j.message?.role==="assistant"&&j.message?.content){if(Array.isArray(j.message.content)){for(const D of j.message.content)if(D.type==="text"){let G=D.text;typeof G=="string"&&(G=tr(G)),v.push({type:"assistant",content:G,timestamp:j.timestamp||new Date().toISOString()})}else if(D.type==="tool_use"){const G=E.get(D.id);v.push({type:"assistant",content:"",timestamp:j.timestamp||new Date().toISOString(),isToolUse:!0,toolName:D.name,toolInput:JSON.stringify(D.input),toolResult:G?{content:typeof G.content=="string"?G.content:JSON.stringify(G.content),isError:G.isError,toolUseResult:G.toolUseResult}:null,toolError:G?.isError||!1,toolResultTimestamp:G?.timestamp||new Date})}}else if(typeof j.message.content=="string"){let D=j.message.content;D=tr(D),v.push({type:"assistant",content:D,timestamp:j.timestamp||new Date().toISOString()})}}}return v},Zr=s.useMemo(()=>la(ne),[ne]),ut=s.useCallback(()=>{we.current&&(we.current.scrollTop=we.current.scrollHeight)},[]),Yt=s.useCallback(()=>{if(!we.current)return!1;const{scrollTop:i,scrollHeight:v,clientHeight:E}=we.current;return v-i-E<50},[]),Xr=s.useCallback(async i=>{if(!i||J.current||Ee||!Se||!r||!t)return!1;J.current=!0;const v=i.scrollHeight,E=i.scrollTop;try{const j=await Ot(t.name,r.id,!0,"claude");return j.length>0?(se.current={height:v,top:E},Y(D=>[...j,...D]),!0):!1}finally{J.current=!1}},[Se,Ee,r,t,Ot]),Wt=s.useCallback(async()=>{if(we.current){const i=we.current,v=Yt();ke(!v),i.scrollTop<100?me.current||await Xr(i)&&(me.current=!0):me.current=!1}},[Yt,Xr]);s.useLayoutEffect(()=>{if(!se.current||!we.current)return;const{height:i,top:v}=se.current,E=we.current,D=E.scrollHeight-i;E.scrollTop=v+Math.max(D,0),se.current=null,setTimeout(()=>{me.current=!1},100)},[oe.length]),s.useEffect(()=>{(async()=>{if(r&&t){const v=localStorage.getItem("selected-provider")||"claude";if(Ke.current=!0,M!==null&&M!==r.id?(pe(0),je(!1),ye(0),Et(null),W(!1),a&&o&&o({type:"check-session-status",sessionId:r.id,provider:v})):M===null&&(pe(0),je(!1),ye(0),a&&o&&o({type:"check-session-status",sessionId:r.id,provider:v})),P(r.id),ze)Ce(!1);else{const j=await Ot(t.name,r.id,!1,"claude");Y(j)}}else!ze&&!ae&&(X([]),Y([])),P(null),pe(0),je(!1),ye(0);setTimeout(()=>{Ke.current=!1},250)})()},[r,t,ut,ze]),s.useEffect(()=>{c>0&&r&&t&&(async()=>{try{const v=await Ot(t.name,r.id,!1,"claude");Y(v),S&&Yt()&&setTimeout(()=>ut(),200)}catch(v){console.error("Error reloading messages from external update:",v)}})()},[c,r,t,Ot,Yt,S,ut]),s.useEffect(()=>{ne.length>0&&!ae&&X(Zr)},[Zr,ne,ae]),s.useEffect(()=>{d&&d(Q)},[Q,d]),s.useEffect(()=>{t&&F!==""?He.setItem(`draft_input_${t.name}`,F):t&&F===""&&He.removeItem(`draft_input_${t.name}`)},[F,t]),s.useEffect(()=>{t&&oe.length>0&&He.setItem(`chat_messages_${t.name}`,JSON.stringify(oe))},[oe,t]),s.useEffect(()=>{if(t){const i=He.getItem(`draft_input_${t.name}`)||"";i!==F&&Z(i)}},[t?.name]),s.useEffect(()=>{M&&ae&&h&&h(M)},[ae,M,h]),s.useEffect(()=>{M&&L&&L.has(M)&&!ae&&(W(!0),K(!0))},[M,L]),s.useEffect(()=>{if(n.length>0){const i=n[n.length-1];if(!["projects_updated","session-created","claude-complete"].includes(i.type)&&i.sessionId&&M&&i.sessionId!==M){console.log("⏭️ Skipping message for different session:",i.sessionId,"current:",M);return}switch(i.type){case"session-created":i.sessionId&&!M&&(sessionStorage.setItem("pendingSessionId",i.sessionId),Ce(!0),k&&k(i.sessionId),_(H=>H.map(ee=>ee.sessionId?ee:{...ee,sessionId:i.sessionId})));break;case"token-budget":i.data&&Et(i.data);break;case"claude-response":const j=i.data.message||i.data;if(j&&typeof j=="object"&&j.type){if(j.type==="content_block_delta"&&j.delta?.text){const H=Mt(j.delta.text);re.current+=H,xe.current||(xe.current=setTimeout(()=>{const ee=re.current;re.current="",xe.current=null,ee&&X(fe=>{const De=[...fe],We=De[De.length-1];return We&&We.type==="assistant"&&!We.isToolUse&&We.isStreaming?We.content=(We.content||"")+ee:De.push({type:"assistant",content:ee,timestamp:new Date,isStreaming:!0}),De})},100));return}if(j.type==="content_block_stop"){xe.current&&(clearTimeout(xe.current),xe.current=null);const H=re.current;re.current="",H&&X(ee=>{const fe=[...ee],De=fe[fe.length-1];return De&&De.type==="assistant"&&!De.isToolUse&&De.isStreaming?De.content=(De.content||"")+H:fe.push({type:"assistant",content:H,timestamp:new Date,isStreaming:!0}),fe}),X(ee=>{const fe=[...ee],De=fe[fe.length-1];return De&&De.type==="assistant"&&De.isStreaming&&(De.isStreaming=!1),fe});return}}if(i.data.type==="system"&&i.data.subtype==="init"&&i.data.session_id&&M&&i.data.session_id!==M){console.log("🔄 Claude CLI session duplication detected:",{originalSession:M,newSession:i.data.session_id}),Ce(!0),C&&C(i.data.session_id);return}if(i.data.type==="system"&&i.data.subtype==="init"&&i.data.session_id&&!M){console.log("🔄 New session init detected:",{newSession:i.data.session_id}),Ce(!0),C&&C(i.data.session_id);return}if(i.data.type==="system"&&i.data.subtype==="init"&&i.data.session_id&&M&&i.data.session_id===M){console.log("🔄 System init message for current session, ignoring");return}if(Array.isArray(j.content)){for(const H of j.content)if(H.type==="tool_use"){const ee=H.input?JSON.stringify(H.input,null,2):"";X(fe=>[...fe,{type:"assistant",content:"",timestamp:new Date,isToolUse:!0,toolName:H.name,toolInput:ee,toolId:H.id,toolResult:null}])}else if(H.type==="text"&&H.text?.trim()){let ee=Mt(H.text);ee=Ar(ee),X(fe=>[...fe,{type:"assistant",content:ee,timestamp:new Date}])}}else if(typeof j.content=="string"&&j.content.trim()){let H=Mt(j.content);H=Ar(H),X(ee=>[...ee,{type:"assistant",content:H,timestamp:new Date}])}if(j.role==="user"&&Array.isArray(j.content))for(const H of j.content)H.type==="tool_result"&&X(ee=>ee.map(fe=>fe.isToolUse&&fe.toolId===H.tool_use_id?{...fe,toolResult:{content:H.content,isError:H.is_error,timestamp:new Date}}:fe));break;case"claude-output":{const H=String(i.data||"");H.trim()&&(re.current+=re.current?`
|
|
72
72
|
${H}`:H,xe.current||(xe.current=setTimeout(()=>{const ee=re.current;re.current="",xe.current=null,ee&&X(fe=>{const De=[...fe],We=De[De.length-1];return We&&We.type==="assistant"&&!We.isToolUse&&We.isStreaming?We.content=We.content?`${We.content}
|
|
73
73
|
${ee}`:ee:De.push({type:"assistant",content:ee,timestamp:new Date,isStreaming:!0}),De})},100)))}break;case"claude-interactive-prompt":X(H=>[...H,{type:"assistant",content:i.data,timestamp:new Date,isInteractivePrompt:!0}]);break;case"claude-permission-request":{if(!i.requestId)break;_(H=>H.some(ee=>ee.requestId===i.requestId)?H:[...H,{requestId:i.requestId,toolName:i.toolName||"UnknownTool",input:i.input,context:i.context,sessionId:i.sessionId||null,receivedAt:new Date}]),W(!0),K(!0),yt({text:"等待权限",tokens:0,can_interrupt:!0});break}case"claude-permission-cancelled":{if(!i.requestId)break;_(H=>H.filter(ee=>ee.requestId!==i.requestId));break}case"claude-error":X(H=>[...H,{type:"error",content:`错误: ${i.error}`,timestamp:new Date}]);break;case"claude-complete":const D=i.sessionId||M||sessionStorage.getItem("pendingSessionId");(D===M||!M)&&(W(!1),K(!1),yt(null)),D&&(g&&g(D),y&&y(D));const G=sessionStorage.getItem("pendingSessionId");G&&!M&&i.exitCode===0&&(P(G),sessionStorage.removeItem("pendingSessionId"),console.log("✅ New session complete, ID set to:",G)),t&&i.exitCode===0&&He.removeItem(`chat_messages_${t.name}`),_([]);break;case"session-aborted":{const H=i.sessionId||M;H===M&&(W(!1),K(!1),yt(null)),H&&(g&&g(H),y&&y(H)),_([]),X(ee=>[...ee,{type:"assistant",content:"会话已被用户中断。",timestamp:new Date}]);break}case"session-status":{const H=i.sessionId;(H===M||r&&H===r.id)&&i.isProcessing&&(W(!0),K(!0),h&&h(H));break}case"claude-status":const de=i.data;if(de){let H={text:"处理中...",tokens:0,can_interrupt:!0};de.message?H.text=de.message:de.status?H.text=de.status:typeof de=="string"&&(H.text=de),de.tokens?H.tokens=de.tokens:de.token_count&&(H.tokens=de.token_count),de.can_interrupt!==void 0&&(H.can_interrupt=de.can_interrupt),yt(H),W(!0),K(H.can_interrupt)}break}}},[n]),s.useEffect(()=>{t&&ca()},[t]);const ca=async()=>{try{const i=await ve.getFiles(t.name);if(i.ok){const v=await i.json(),E=es(v);ur(E)}}catch(i){console.error("Error fetching files:",i)}},es=(i,v="")=>{let E=[];for(const j of i){const D=v?`${v}/${j.name}`:j.name;j.type==="directory"&&j.children?E=E.concat(es(j.children,D)):j.type==="file"&&E.push({name:j.name,path:D,relativePath:j.path})}return E};s.useEffect(()=>{const i=F.slice(0,Ht),v=i.lastIndexOf("@");if(v!==-1){const E=i.slice(v+1);if(E.includes(" "))qe(!1),at(-1);else{at(v),qe(!0);const j=qt.filter(D=>D.name.toLowerCase().includes(E.toLowerCase())||D.path.toLowerCase().includes(E.toLowerCase())).slice(0,10);bt(j),Ft(-1)}}else qe(!1),at(-1)},[F,Ht,qt]),s.useEffect(()=>{const i=setTimeout(()=>{Xe(F)},150);return()=>clearTimeout(i)},[F]);const ts=s.useMemo(()=>oe.length<=At?oe:oe.slice(-At),[oe,At]);s.useEffect(()=>{if(!S&&we.current){const i=we.current;it.current={height:i.scrollHeight,top:i.scrollTop}}}),s.useEffect(()=>{if(we.current&&oe.length>0)if(S)ce||setTimeout(()=>ut(),50);else{const i=we.current,v=it.current.height,E=it.current.top,D=i.scrollHeight-v;D>0&&E>0&&(i.scrollTop=E+D)}},[oe.length,ce,ut,S]),s.useEffect(()=>{we.current&&oe.length>0&&!Ke.current&&(ke(!1),setTimeout(()=>{ut()},200))},[r?.id,t?.name]),s.useEffect(()=>{const i=we.current;if(i)return i.addEventListener("scroll",Wt),()=>i.removeEventListener("scroll",Wt)},[Wt]),s.useEffect(()=>{if(A.current){A.current.style.height="auto",A.current.style.height=A.current.scrollHeight+"px";const i=parseInt(window.getComputedStyle(A.current).lineHeight),v=A.current.scrollHeight>i*2;xt(v)}},[]),s.useEffect(()=>{A.current&&!F.trim()&&(A.current.style.height="auto",xt(!1))},[F]),s.useEffect(()=>{if(!t||!r?.id||r.id.startsWith("new-session-")){Et(null);return}(async()=>{try{const v=`/api/projects/${t.name}/sessions/${r.id}/token-usage`,E=await ie(v);if(E.ok){const j=await E.json();Et(j)}else Et(null)}catch(v){console.error("Failed to fetch initial token usage:",v)}})()},[r?.id,r?.__provider,t?.path]);const da=s.useCallback(i=>{i.trim()&&Z(v=>{const E=v.trim()?`${v} ${i}`:i;return setTimeout(()=>{if(A.current){A.current.style.height="auto",A.current.style.height=A.current.scrollHeight+"px";const j=parseInt(window.getComputedStyle(A.current).lineHeight),D=A.current.scrollHeight>j*2;xt(D)}},0),E})},[]),ma=s.useCallback(()=>{ea(i=>i+100)},[]),Qt=s.useCallback(i=>{const v=i.filter(E=>{try{if(!E||typeof E!="object")return console.warn("Invalid file object:",E),!1;if(!E.type||!E.type.startsWith("image/"))return!1;if(!E.size||E.size>5242880){const j=E.name||"Unknown file";return Ue(D=>{const G=new Map(D);return G.set(j,"文件太大(最大 5MB)"),G}),!1}return!0}catch(j){return console.error("Error validating file:",j,E),!1}});v.length>0&&ue(E=>[...E,...v].slice(0,5))},[]),ua=s.useCallback(async i=>{const v=i.target.files;if(!(!v||v.length===0||!t)){$(!0);try{const E=new FormData;Array.from(v).forEach(G=>{E.append("files",G)});const j=await ve.uploadFiles(t.name,E);if(!j.ok){const G=await j.json();throw new Error(G.error||"上传失败")}const D=await j.json();if(D.files&&D.files.length>0){const G=D.files.map(H=>`@${H.name}`).join(" "),de=F?`${F} ${G} `:`${G} `;Z(de),A.current&&A.current.focus()}}catch(E){console.error("File upload failed:",E)}finally{$(!1),O.current&&(O.current.value="")}}},[t,F]),pa=s.useCallback(async i=>{const v=Array.from(i.clipboardData.items);for(const E of v)if(E.type.startsWith("image/")){const j=E.getAsFile();j&&Qt([j])}if(v.length===0&&i.clipboardData.files.length>0){const j=Array.from(i.clipboardData.files).filter(D=>D.type.startsWith("image/"));j.length>0&&Qt(j)}},[Qt]),{getRootProps:xa,getInputProps:ga,isDragActive:ha,open:fa}=Jr({accept:{"image/*":[".png",".jpg",".jpeg",".gif",".webp",".svg"]},maxSize:5*1024*1024,maxFiles:5,onDrop:Qt,noClick:!0,noKeyboard:!0}),jt=s.useCallback(async i=>{if(i.preventDefault(),!F.trim()||ae||!t)return;const v=F.trim();if(v.startsWith("/")){const ee=v.indexOf(" "),fe=ee!==-1?v.slice(0,ee):v,De=Qe.find(We=>We.name===fe&&We.namespace==="builtin");if(De){Gt(De);return}}if(U){const ee=await U();if(ee&&!ee.allowed){q?.(ee.reason);return}}let E=[];if(le.length>0){const ee=new FormData;le.forEach(fe=>{ee.append("images",fe)});try{const fe=await ie(`/api/projects/${t.name}/upload-images`,{method:"POST",headers:{},body:ee});if(!fe.ok)throw new Error("图片上传失败");E=(await fe.json()).images}catch(fe){console.error("Image upload failed:",fe),X(De=>[...De,{type:"error",content:`图片上传失败: ${fe.message}`,timestamp:new Date}]);return}}const j={type:"user",content:F,images:E,timestamp:new Date};X(ee=>[...ee,j]),W(!0),K(!0),yt({text:"处理中",tokens:0,can_interrupt:!0}),ke(!1),setTimeout(()=>ut(),100);const G=M||r?.id||`new-session-${Date.now()}`;u&&u(G);const H=(()=>{try{const ee=He.getItem("claude-settings");if(ee)return JSON.parse(ee)}catch(ee){console.error("Error loading tools settings:",ee)}return{allowedTools:[],disallowedTools:[],skipPermissions:!1}})();o({type:"claude-command",command:F,options:{projectPath:t.path,cwd:t.fullPath,sessionId:M,resume:!!M,toolsSettings:H,permissionMode:B,model:ht,images:E}}),Z(""),ue([]),Be(new Map),Ue(new Map),xt(!1),A.current&&(A.current.style.height="auto"),t&&He.removeItem(`draft_input_${t.name}`)},[F,ae,t,le,M,r,mt,B,u,ht,o,Z,ue,Be,Ue,xt,A,X,W,K,yt,ke,ut,U,q,Gt,Qe]),rs=s.useCallback(i=>!i||mt!=="claude"?{success:!1}:Fo(i.entry),[mt]),fr=s.useCallback((i,v)=>{const j=(Array.isArray(i)?i:[i]).filter(Boolean);j.length!==0&&(j.forEach(D=>{o({type:"claude-permission-response",requestId:D,allow:!!v?.allow,updatedInput:v?.updatedInput,message:v?.message,rememberEntry:v?.rememberEntry})}),_(D=>{const G=D.filter(de=>!j.includes(de.requestId));return G.length===0&&yt(null),G}))},[o]);s.useEffect(()=>{hr.current=jt},[jt]);const ss=i=>{if(!i)return;const v=F.slice(0,Yr),E=F.slice(Yr),j=E.indexOf(" "),D=j!==-1?E.slice(j):"",G=v+i.name+" "+D;Z(G),lt(!1),gt(-1),ct(""),dt(-1),_e.current&&clearTimeout(_e.current),Gt(i)},ba=i=>{if(Ye&&vt.length>0){if(i.key==="ArrowDown"){i.preventDefault(),dt(v=>v<vt.length-1?v+1:0);return}if(i.key==="ArrowUp"){i.preventDefault(),dt(v=>v>0?v-1:vt.length-1);return}if(i.key==="Tab"||i.key==="Enter"){i.preventDefault(),xr>=0?ss(vt[xr]):vt.length>0&&ss(vt[0]);return}if(i.key==="Escape"){i.preventDefault(),lt(!1),gt(-1),ct(""),dt(-1),_e.current&&clearTimeout(_e.current);return}}if(pt&&st.length>0){if(i.key==="ArrowDown"){i.preventDefault(),Ft(v=>v<st.length-1?v+1:0);return}if(i.key==="ArrowUp"){i.preventDefault(),Ft(v=>v>0?v-1:st.length-1);return}if(i.key==="Tab"||i.key==="Enter"){i.preventDefault(),St>=0?br(st[St]):st.length>0&&br(st[0]);return}if(i.key==="Escape"){i.preventDefault(),qe(!1);return}}if(i.key==="Tab"&&!pt&&!Ye){i.preventDefault();const v=["default","acceptEdits","bypassPermissions","plan"],j=(v.indexOf(B)+1)%v.length,D=v[j];f(D),r?.id&&localStorage.setItem(`permissionMode-${r.id}`,D);return}if(i.key==="Enter"){if(i.nativeEvent.isComposing)return;(i.ctrlKey||i.metaKey)&&!i.shiftKey?(i.preventDefault(),jt(i)):!i.shiftKey&&!i.ctrlKey&&!i.metaKey&&(p||(i.preventDefault(),jt(i)))}},br=i=>{const v=F.slice(0,Jt),E=F.slice(Jt),j=E.indexOf(" "),D=j!==-1?E.slice(j):"",G=v+"@"+i.path+" "+D,de=v.length+1+i.path.length+1;A.current&&!A.current.matches(":focus")&&A.current.focus(),Z(G),Ct(de),qe(!1),at(-1),A.current&&requestAnimationFrame(()=>{A.current&&(A.current.setSelectionRange(de,de),A.current.matches(":focus")||A.current.focus())})},va=i=>{const v=i.target.value,E=i.target.selectionStart;if(!M&&v.trim(),Z(v),Ct(E),!v.trim()){i.target.style.height="auto",xt(!1),lt(!1),gt(-1),ct("");return}const j=v.slice(0,E);if((j.match(/```/g)||[]).length%2===1){lt(!1),gt(-1),ct("");return}const de=/(^|\s)\/(\S*)$/,H=j.match(de);if(H){const ee=H.index+H[1].length,fe=H[2];gt(ee),lt(!0),dt(-1),_e.current&&clearTimeout(_e.current),_e.current=setTimeout(()=>{ct(fe)},150)}else lt(!1),gt(-1),ct(""),_e.current&&clearTimeout(_e.current)},ya=i=>{Ct(i.target.selectionStart)},ja=()=>{M&&T&&o({type:"abort-session",sessionId:M,provider:mt})},wa=()=>{const i=["default","acceptEdits","bypassPermissions","plan"],E=(i.indexOf(B)+1)%i.length,j=i[E];f(j),r?.id&&localStorage.setItem(`permissionMode-${r.id}`,j)};return t?e.jsxs(e.Fragment,{children:[e.jsx("style",{children:`
|
|
74
74
|
details[open] .details-chevron {
|
package/dist/index.html
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<!-- Prevent zoom on iOS -->
|
|
27
27
|
<meta name="format-detection" content="telephone=no" />
|
|
28
|
-
<script type="module" crossorigin src="/assets/index-
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-BV3BueqH.js"></script>
|
|
29
29
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BeVl62c0.js">
|
|
30
30
|
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-C_VWDoZS.js">
|
|
31
31
|
<link rel="modulepreload" crossorigin href="/assets/vendor-utils-00TdZexr.js">
|
package/package.json
CHANGED
package/server/claude-sdk.js
CHANGED
|
@@ -644,13 +644,22 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
644
644
|
const cacheReadTokens = modelData.cacheReadInputTokens || 0;
|
|
645
645
|
const cacheCreationTokens = modelData.cacheCreationInputTokens || 0;
|
|
646
646
|
|
|
647
|
+
// Extract detailed cache creation tokens if available
|
|
648
|
+
const cacheCreation = modelData.cacheCreation || {};
|
|
649
|
+
const cacheCreation5mTokens = cacheCreation.ephemeral5mInputTokens || 0;
|
|
650
|
+
const cacheCreation1hTokens = cacheCreation.ephemeral1hInputTokens || 0;
|
|
651
|
+
const hasPreciseCacheData = cacheCreation5mTokens > 0 || cacheCreation1hTokens > 0;
|
|
652
|
+
|
|
647
653
|
const normalizedModel = normalizeModelName(modelKey);
|
|
648
654
|
const cost = calculateCost({
|
|
649
655
|
model: normalizedModel,
|
|
650
656
|
inputTokens,
|
|
651
657
|
outputTokens,
|
|
652
658
|
cacheReadTokens,
|
|
653
|
-
|
|
659
|
+
// Use precise data if available, otherwise fallback to legacy field
|
|
660
|
+
cacheCreation5mTokens: hasPreciseCacheData ? cacheCreation5mTokens : undefined,
|
|
661
|
+
cacheCreation1hTokens: hasPreciseCacheData ? cacheCreation1hTokens : undefined,
|
|
662
|
+
cacheCreationTokens: hasPreciseCacheData ? undefined : cacheCreationTokens
|
|
654
663
|
});
|
|
655
664
|
|
|
656
665
|
// Insert usage record
|
|
@@ -15,21 +15,24 @@ const PRICING_PER_MILLION = {
|
|
|
15
15
|
input: 5.00,
|
|
16
16
|
output: 25.00,
|
|
17
17
|
cacheRead: 0.50,
|
|
18
|
-
|
|
18
|
+
cacheCreate5m: 6.25, // 1.25x base (5-minute ephemeral cache)
|
|
19
|
+
cacheCreate1h: 10.00 // 2x base (1-hour extended cache)
|
|
19
20
|
},
|
|
20
21
|
// Claude Sonnet 4.5
|
|
21
22
|
'claude-sonnet-4-5-20250929': {
|
|
22
23
|
input: 3.00,
|
|
23
24
|
output: 15.00,
|
|
24
25
|
cacheRead: 0.30,
|
|
25
|
-
|
|
26
|
+
cacheCreate5m: 3.75, // 1.25x base
|
|
27
|
+
cacheCreate1h: 6.00 // 2x base
|
|
26
28
|
},
|
|
27
29
|
// Claude Haiku 4.5
|
|
28
30
|
'claude-haiku-4-5-20251001': {
|
|
29
31
|
input: 1.00,
|
|
30
32
|
output: 5.00,
|
|
31
33
|
cacheRead: 0.10,
|
|
32
|
-
|
|
34
|
+
cacheCreate5m: 1.25, // 1.25x base
|
|
35
|
+
cacheCreate1h: 2.00 // 2x base
|
|
33
36
|
},
|
|
34
37
|
// ============ Legacy Models ============
|
|
35
38
|
// Claude Opus 4.1
|
|
@@ -37,42 +40,48 @@ const PRICING_PER_MILLION = {
|
|
|
37
40
|
input: 15.00,
|
|
38
41
|
output: 75.00,
|
|
39
42
|
cacheRead: 1.50,
|
|
40
|
-
|
|
43
|
+
cacheCreate5m: 18.75, // 1.25x base
|
|
44
|
+
cacheCreate1h: 30.00 // 2x base
|
|
41
45
|
},
|
|
42
46
|
// Claude Opus 4
|
|
43
47
|
'claude-opus-4-20250514': {
|
|
44
48
|
input: 15.00,
|
|
45
49
|
output: 75.00,
|
|
46
50
|
cacheRead: 1.50,
|
|
47
|
-
|
|
51
|
+
cacheCreate5m: 18.75, // 1.25x base
|
|
52
|
+
cacheCreate1h: 30.00 // 2x base
|
|
48
53
|
},
|
|
49
54
|
// Claude Sonnet 4
|
|
50
55
|
'claude-sonnet-4-20250514': {
|
|
51
56
|
input: 3.00,
|
|
52
57
|
output: 15.00,
|
|
53
58
|
cacheRead: 0.30,
|
|
54
|
-
|
|
59
|
+
cacheCreate5m: 3.75, // 1.25x base
|
|
60
|
+
cacheCreate1h: 6.00 // 2x base
|
|
55
61
|
},
|
|
56
62
|
// Claude Sonnet 3.7
|
|
57
63
|
'claude-3-7-sonnet-20250219': {
|
|
58
64
|
input: 3.00,
|
|
59
65
|
output: 15.00,
|
|
60
66
|
cacheRead: 0.30,
|
|
61
|
-
|
|
67
|
+
cacheCreate5m: 3.75, // 1.25x base
|
|
68
|
+
cacheCreate1h: 6.00 // 2x base
|
|
62
69
|
},
|
|
63
70
|
// Claude Haiku 3.5
|
|
64
71
|
'claude-3-5-haiku-20241022': {
|
|
65
72
|
input: 0.80,
|
|
66
73
|
output: 4.00,
|
|
67
74
|
cacheRead: 0.08,
|
|
68
|
-
|
|
75
|
+
cacheCreate5m: 1.00, // 1.25x base
|
|
76
|
+
cacheCreate1h: 1.60 // 2x base
|
|
69
77
|
},
|
|
70
78
|
// Claude Haiku 3
|
|
71
79
|
'claude-3-haiku-20240307': {
|
|
72
80
|
input: 0.25,
|
|
73
81
|
output: 1.25,
|
|
74
82
|
cacheRead: 0.03,
|
|
75
|
-
|
|
83
|
+
cacheCreate5m: 0.30, // Per pricing doc (not exactly 1.25x)
|
|
84
|
+
cacheCreate1h: 0.50 // 2x base
|
|
76
85
|
},
|
|
77
86
|
// ============ Aliases ============
|
|
78
87
|
// Aliases for simplified model names (pointing to latest versions)
|
|
@@ -80,19 +89,22 @@ const PRICING_PER_MILLION = {
|
|
|
80
89
|
input: 3.00,
|
|
81
90
|
output: 15.00,
|
|
82
91
|
cacheRead: 0.30,
|
|
83
|
-
|
|
92
|
+
cacheCreate5m: 3.75, // 1.25x base
|
|
93
|
+
cacheCreate1h: 6.00 // 2x base
|
|
84
94
|
},
|
|
85
95
|
'opus': {
|
|
86
96
|
input: 5.00,
|
|
87
97
|
output: 25.00,
|
|
88
98
|
cacheRead: 0.50,
|
|
89
|
-
|
|
99
|
+
cacheCreate5m: 6.25, // 1.25x base
|
|
100
|
+
cacheCreate1h: 10.00 // 2x base
|
|
90
101
|
},
|
|
91
102
|
'haiku': {
|
|
92
103
|
input: 1.00,
|
|
93
104
|
output: 5.00,
|
|
94
105
|
cacheRead: 0.10,
|
|
95
|
-
|
|
106
|
+
cacheCreate5m: 1.25, // 1.25x base
|
|
107
|
+
cacheCreate1h: 2.00 // 2x base
|
|
96
108
|
}
|
|
97
109
|
};
|
|
98
110
|
|
|
@@ -103,7 +115,8 @@ for (const [model, prices] of Object.entries(PRICING_PER_MILLION)) {
|
|
|
103
115
|
input: prices.input / 1_000_000,
|
|
104
116
|
output: prices.output / 1_000_000,
|
|
105
117
|
cacheRead: prices.cacheRead / 1_000_000,
|
|
106
|
-
|
|
118
|
+
cacheCreate5m: prices.cacheCreate5m / 1_000_000,
|
|
119
|
+
cacheCreate1h: prices.cacheCreate1h / 1_000_000
|
|
107
120
|
};
|
|
108
121
|
}
|
|
109
122
|
|
|
@@ -144,7 +157,9 @@ function normalizeModelName(model) {
|
|
|
144
157
|
* @param {number} usage.inputTokens - Input tokens
|
|
145
158
|
* @param {number} usage.outputTokens - Output tokens
|
|
146
159
|
* @param {number} usage.cacheReadTokens - Cache read tokens
|
|
147
|
-
* @param {number} usage.
|
|
160
|
+
* @param {number} usage.cacheCreation5mTokens - 5-minute ephemeral cache creation tokens
|
|
161
|
+
* @param {number} usage.cacheCreation1hTokens - 1-hour extended cache creation tokens
|
|
162
|
+
* @param {number} [usage.cacheCreationTokens] - Legacy: total cache creation tokens (fallback for SDK that doesn't distinguish)
|
|
148
163
|
* @returns {number} Cost in USD
|
|
149
164
|
*/
|
|
150
165
|
function calculateCost(usage) {
|
|
@@ -159,7 +174,17 @@ function calculateCost(usage) {
|
|
|
159
174
|
const inputCost = (usage.inputTokens || 0) * prices.input;
|
|
160
175
|
const outputCost = (usage.outputTokens || 0) * prices.output;
|
|
161
176
|
const cacheReadCost = (usage.cacheReadTokens || 0) * prices.cacheRead;
|
|
162
|
-
|
|
177
|
+
|
|
178
|
+
// Calculate cache creation cost with distinction between 5m and 1h
|
|
179
|
+
let cacheCreateCost = 0;
|
|
180
|
+
if (usage.cacheCreation5mTokens !== undefined || usage.cacheCreation1hTokens !== undefined) {
|
|
181
|
+
// Use precise calculation with separate 5m and 1h tokens
|
|
182
|
+
cacheCreateCost = (usage.cacheCreation5mTokens || 0) * prices.cacheCreate5m +
|
|
183
|
+
(usage.cacheCreation1hTokens || 0) * prices.cacheCreate1h;
|
|
184
|
+
} else {
|
|
185
|
+
// Fallback: use legacy cacheCreationTokens with 5m price (default assumption)
|
|
186
|
+
cacheCreateCost = (usage.cacheCreationTokens || 0) * prices.cacheCreate5m;
|
|
187
|
+
}
|
|
163
188
|
|
|
164
189
|
return inputCost + outputCost + cacheReadCost + cacheCreateCost;
|
|
165
190
|
}
|
|
@@ -209,14 +209,26 @@ async function scanSessionFile(userUuid, sessionId, filePath, startLine) {
|
|
|
209
209
|
const inputTokens = usage.input_tokens || 0;
|
|
210
210
|
const outputTokens = usage.output_tokens || 0;
|
|
211
211
|
const cacheReadTokens = usage.cache_read_input_tokens || 0;
|
|
212
|
-
|
|
212
|
+
|
|
213
|
+
// Extract detailed cache creation tokens from nested cache_creation object
|
|
214
|
+
// Structure: usage.cache_creation.ephemeral_5m_input_tokens / ephemeral_1h_input_tokens
|
|
215
|
+
const cacheCreation = usage.cache_creation || {};
|
|
216
|
+
const cacheCreation5mTokens = cacheCreation.ephemeral_5m_input_tokens || 0;
|
|
217
|
+
const cacheCreation1hTokens = cacheCreation.ephemeral_1h_input_tokens || 0;
|
|
218
|
+
|
|
219
|
+
// Fallback to legacy field if nested object not present
|
|
220
|
+
const totalCacheCreationTokens = usage.cache_creation_input_tokens || 0;
|
|
221
|
+
const hasPreciseCacheData = cacheCreation5mTokens > 0 || cacheCreation1hTokens > 0;
|
|
213
222
|
|
|
214
223
|
const cost = calculateCost({
|
|
215
224
|
model,
|
|
216
225
|
inputTokens,
|
|
217
226
|
outputTokens,
|
|
218
227
|
cacheReadTokens,
|
|
219
|
-
|
|
228
|
+
// Use precise data if available, otherwise fallback to legacy field
|
|
229
|
+
cacheCreation5mTokens: hasPreciseCacheData ? cacheCreation5mTokens : undefined,
|
|
230
|
+
cacheCreation1hTokens: hasPreciseCacheData ? cacheCreation1hTokens : undefined,
|
|
231
|
+
cacheCreationTokens: hasPreciseCacheData ? undefined : totalCacheCreationTokens
|
|
220
232
|
});
|
|
221
233
|
|
|
222
234
|
// Determine the date from the entry timestamp or use current date
|
|
@@ -227,7 +239,7 @@ async function scanSessionFile(userUuid, sessionId, filePath, startLine) {
|
|
|
227
239
|
// Uses session_id (or user_uuid) + model + all token counts + time window to match
|
|
228
240
|
if (usageDb.checkRecordExists(
|
|
229
241
|
userUuid, sessionId, model, inputTokens, outputTokens,
|
|
230
|
-
cacheReadTokens,
|
|
242
|
+
cacheReadTokens, totalCacheCreationTokens, entryTimestamp
|
|
231
243
|
)) {
|
|
232
244
|
// Record already exists (likely from SDK), skip to avoid duplicate counting
|
|
233
245
|
continue;
|
|
@@ -241,7 +253,7 @@ async function scanSessionFile(userUuid, sessionId, filePath, startLine) {
|
|
|
241
253
|
input_tokens: inputTokens,
|
|
242
254
|
output_tokens: outputTokens,
|
|
243
255
|
cache_read_tokens: cacheReadTokens,
|
|
244
|
-
cache_creation_tokens:
|
|
256
|
+
cache_creation_tokens: totalCacheCreationTokens,
|
|
245
257
|
cost_usd: cost,
|
|
246
258
|
source: 'cli',
|
|
247
259
|
created_at: entryTimestamp
|