@kunal_dutt/token-lens 0.2.54 → 0.2.55
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.
- package/dist-helper/core.js +1 -1
- package/package.json +1 -1
- package/server.mjs +1 -1
- package/token-lens.mcpb +0 -0
package/dist-helper/core.js
CHANGED
|
@@ -3333,4 +3333,4 @@ ${" ".repeat(e)}}`;default:return"any"}}function ja(i){let e=["namespace functio
|
|
|
3333
3333
|
`),O=g[y].trim().endsWith(",")?",":"",ke=s(O?A.replace(/,\s*$/,""):A,`lines ${S+1}-${y+1}`);if(ke!==A&&ke!==A.replace(/,\s*$/,"")){w.push(T[1]+ke+O),S=y;continue}}}w.push(g[S])}let C=w.join(`
|
|
3334
3334
|
`),E=C.replace(/[ \t]+$/gm,"").replace(/\n{3,}/g,`
|
|
3335
3335
|
|
|
3336
|
-
`);return E!==C&&(h=!0),C=E,C});return n.length&&r.push(`removed indentation and line breaks from ${n.length} JSON block${n.length>1?"s":""} (same data)`),h&&r.push("removed trailing spaces and extra blank lines outside code"),o(k.join(""),n.length||h?void 0:"Nothing to compact: no pretty-printed JSON or extra whitespace found.")}var v=i=>i,G={"gpt-astra":v({id:"gpt-astra",label:"GPT-6 Astra",family:"openai",contextWindow:105e4,tokenizer:"heuristic",charsPerToken:4.2,reasoning:!0,pricing:{inputPerMTok:10,outputPerMTok:50}}),"gpt-reasoning":v({id:"gpt-reasoning",label:"GPT (thinking)",family:"openai",contextWindow:196e3,tokenizer:"o200k",charsPerToken:4.2,reasoning:!0,lighterAlternative:"the Instant / non-thinking model",pricing:{inputPerMTok:5,outputPerMTok:30,cachedInputPerMTok:.5}}),gpt:v({id:"gpt",label:"GPT",family:"openai",contextWindow:128e3,tokenizer:"o200k",charsPerToken:4.2,reasoning:!1,pricing:{inputPerMTok:2.5,outputPerMTok:15,cachedInputPerMTok:.25}}),"claude-fable":v({id:"claude-fable",label:"Claude Fable",family:"anthropic",contextWindow:1e6,tokenizer:"heuristic",charsPerToken:3.5,reasoning:!0,notes:["Claude reads XML-style tags well: wrap pasted material in <doc>\u2026</doc> instead of explaining it."],pricing:{inputPerMTok:10,outputPerMTok:50,cachedInputPerMTok:.25}}),"claude-opus":v({id:"claude-opus",label:"Claude Opus",family:"anthropic",contextWindow:2e5,tokenizer:"heuristic",charsPerToken:3.5,reasoning:!1,lighterAlternative:"Sonnet or Haiku",notes:["Claude reads XML-style tags well: wrap pasted material in <doc>\u2026</doc> instead of explaining it."],pricing:{inputPerMTok:4,outputPerMTok:20,cachedInputPerMTok:.2}}),"claude-sonnet":v({id:"claude-sonnet",label:"Claude Sonnet",family:"anthropic",contextWindow:2e5,tokenizer:"heuristic",charsPerToken:3.5,reasoning:!1,lighterAlternative:"Haiku",notes:["Claude reads XML-style tags well: wrap pasted material in <doc>\u2026</doc> instead of explaining it."],pricing:{inputPerMTok:2,outputPerMTok:10,cachedInputPerMTok:.2}}),"claude-haiku":v({id:"claude-haiku",label:"Claude Haiku",family:"anthropic",contextWindow:2e5,tokenizer:"heuristic",charsPerToken:3.5,reasoning:!1,pricing:{inputPerMTok:.1,outputPerMTok:.5,cachedInputPerMTok:.01}}),"gemini-pro":v({id:"gemini-pro",label:"Gemini Pro",family:"google",contextWindow:1e6,tokenizer:"heuristic",charsPerToken:4,reasoning:!0,lighterAlternative:"Flash",pricing:{inputPerMTok:2,outputPerMTok:12,cachedInputPerMTok:.2,longContext:{aboveTokens:2e5,inputPerMTok:4,outputPerMTok:18}}}),"gemini-flash":v({id:"gemini-flash",label:"Gemini Flash",family:"google",contextWindow:1e6,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:1.5,outputPerMTok:9,cachedInputPerMTok:.15}}),deepseek:v({id:"deepseek",label:"DeepSeek",family:"deepseek",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:.28,outputPerMTok:.42,cachedInputPerMTok:.028}}),"deepseek-reasoning":v({id:"deepseek-reasoning",label:"DeepSeek (thinking)",family:"deepseek",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!0,lighterAlternative:"the non-thinking mode",pricing:{inputPerMTok:.55,outputPerMTok:2.19,cachedInputPerMTok:.14}}),mistral:v({id:"mistral",label:"Mistral",family:"mistral",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:2,outputPerMTok:6}}),grok:v({id:"grok",label:"Grok",family:"xai",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:2,outputPerMTok:6}}),llama:v({id:"llama",label:"Llama",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.9,reasoning:!1,pricing:{inputPerMTok:.27,outputPerMTok:.85}}),qwen:v({id:"qwen",label:"Qwen",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:.4,outputPerMTok:1.2}}),kimi:v({id:"kimi",label:"Kimi",family:"other",contextWindow:256e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:.6,outputPerMTok:2.5,cachedInputPerMTok:.15}}),glm:v({id:"glm",label:"GLM",family:"other",contextWindow:2e5,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:.6,outputPerMTok:2.2,cachedInputPerMTok:.11}}),command:v({id:"command",label:"Cohere Command",family:"other",contextWindow:256e3,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:2.5,outputPerMTok:10}}),sonar:v({id:"sonar",label:"Perplexity Sonar",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:1,outputPerMTok:1}}),gemma:v({id:"gemma",label:"Gemma",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.9,reasoning:!1,pricing:{inputPerMTok:.1,outputPerMTok:.2}}),phi:v({id:"phi",label:"Phi",family:"other",contextWindow:16e3,tokenizer:"heuristic",charsPerToken:3.9,reasoning:!1,pricing:{inputPerMTok:.07,outputPerMTok:.14}}),generic:v({id:"generic",label:"Unknown model",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:2,outputPerMTok:10}})},_i=[[/\b(fable|mythos)\b/,"claude-fable"],[/\bastra\b|\bgpt[- ]?6\b/,"gpt-astra"],[/\bclaude\b.*\bopus\b|\bopus\b/,"claude-opus"],[/\bsonnet\b/,"claude-sonnet"],[/\bhaiku\b/,"claude-haiku"],[/\b(qwen|qwq)/,"qwen"],[/\b(glm|chatglm|zhipu)\b|\bglm-?\d/,"glm"],[/\b(kimi|moonshot)/,"kimi"],[/\bgemma/,"gemma"],[/\bgemini\b.*\bflash\b|\bflash\b/,"gemini-flash"],[/\bgemini\b.*\b(pro|ultra|deep think)\b|^\s*\d(\.\d)?\s*pro\b|\bdeep think\b/,"gemini-pro"],[/\bdeepseek\b.*\b(r1|reason|think|deepthink)|\bdeepthink\b/,"deepseek-reasoning"],[/\bdeepseek\b/,"deepseek"],[/\b(mistral|le chat|magistral|codestral)\b/,"mistral"],[/\bgrok\b/,"grok"],[/\bllama|codellama/,"llama"],[/\bcommand[- ]?(r|a)\b|\bcohere\b/,"command"],[/\bsonar\b|\bperplexity\b/,"sonar"],[/\bphi-?\d/,"phi"],[/\bo[1-9](-mini|-pro)?\b|\bgpt[- ]?\S*\s*(thinking|pro)\b|\bthinking\b/,"gpt-reasoning"],[/\b(gpt|chatgpt)\b|\bgpt-?\d/,"gpt"],[/\bgemini\b/,"gemini-pro"],[/\bclaude\b/,"claude-sonnet"]];var Ei={chatgpt:"gpt",claude:"claude-sonnet",gemini:"gemini-flash",deepseek:"deepseek",mistral:"mistral",grok:"grok",generic:"generic"},Ai=[];var zr={...G};function K(i,e="generic"){let a=(i??"").toLowerCase().replace(/\s+/g," ").trim();if(a){for(let[n,r]of Ai)if(n.test(a))return G[r];if(e==="gemini"&&/\b(pro|deep think)\b/.test(a))return G["gemini-pro"];if(e==="gemini"&&/\bflash\b/.test(a))return G["gemini-flash"];if(e==="deepseek"&&/\b(deepthink|r1|think)/.test(a))return G["deepseek-reasoning"];for(let[n,r]of _i)if(n.test(a))return G[r]}return G[Ei[e]??"generic"]}var va="ai-toolbox.co ChatGPT limits guide",ne={"chatgpt-free":{id:"chatgpt-free",site:"chatgpt",label:"ChatGPT Free",meter:"messages",caps:[]},"chatgpt-go":{id:"chatgpt-go",site:"chatgpt",label:"ChatGPT Go",meter:"messages",caps:[]},"chatgpt-plus":{id:"chatgpt-plus",site:"chatgpt",label:"ChatGPT Plus",meter:"messages",caps:[{models:["gpt"],messages:160,window:"3h",source:va,asOf:"2026-10"},{models:["gpt-reasoning"],messages:3e3,window:"week",source:va,asOf:"2026-10"}]},"chatgpt-pro":{id:"chatgpt-pro",site:"chatgpt",label:"ChatGPT Pro",meter:"messages",caps:[],note:"Pro is close to unlimited for everyday models, but GPT-6 Astra Pro use is capped (number not published)."},"chatgpt-business":{id:"chatgpt-business",site:"chatgpt",label:"ChatGPT Business",meter:"messages",caps:[]},"claude-free":{id:"claude-free",site:"claude",label:"Claude Free",meter:"compute",caps:[]},"claude-pro":{id:"claude-pro",site:"claude",label:"Claude Pro",meter:"compute",caps:[],note:"5-hour session and weekly limits shared across models; Fable runs on usage credits."},"claude-max":{id:"claude-max",site:"claude",label:"Claude Max",meter:"compute",caps:[],note:"5x or 20x Pro's session usage, with a weekly limit across models and one for the top models."},"claude-team":{id:"claude-team",site:"claude",label:"Claude Team",meter:"compute",caps:[]},"gemini-free":{id:"gemini-free",site:"gemini",label:"Gemini (free)",meter:"messages",caps:[]},"gemini-pro":{id:"gemini-pro",site:"gemini",label:"Google AI Pro",meter:"messages",caps:[]},"gemini-ultra":{id:"gemini-ultra",site:"gemini",label:"Google AI Ultra",meter:"messages",caps:[]}};var pe=5*36e5,Ie=7*864e5,Ci=1e3,Ti=500,re=i=>Math.round(i).toLocaleString("en-US");function ge(i){let e=2166136261;for(let a=0;a<i.length;a++)e=Math.imul(e^i.charCodeAt(a),16777619)>>>0;return e.toString(36)}function ya(i){let e=/\b(Free|Pro|Max|Team|Enterprise)\s+plan\b/.exec(i);if(!e)return null;let a=e[1].toLowerCase();return ne[`claude-${a==="enterprise"?"team":a}`]??null}var Ri=["sun","mon","tue","wed","thu","fri","sat"];function Pi(i,e){let a=/\b(sun|mon|tue|wed|thu|fri|sat)\w*\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/i.exec(i);if(!a)return;let n=new Date(e),r=Number(a[2])%12;a[4]?.toLowerCase()==="pm"&&(r+=12),!a[4]&&Number(a[2])>=12&&(r=Number(a[2]));let t=(Ri.indexOf(a[1].slice(0,3).toLowerCase())-n.getDay()+7)%7,o=new Date(n.getFullYear(),n.getMonth(),n.getDate()+t,r,Number(a[3]??0)).getTime();return o<=e?o+Ie:o}function zi(i){let e=/resets?\s+in\s+(?:(\d+)\s*(?:hr|hour|h)s?)?\s*(?:(\d+)\s*(?:min|minute|m)s?)?/i.exec(i);if(!(!e||!e[1]&&!e[2]))return(Number(e[1]??0)*60+Number(e[2]??0))*6e4}function wa(i,e){let a=i.replace(/\s+/g," "),n=/current session(.{0,160}?)(\d{1,3})\s*%\s*used/i.exec(a),r=/weekly (?:limits?|usage)(.{0,260}?)(\d{1,3})\s*%\s*used/i.exec(a),t=!n&&/(\d{1,3})\s*%\s+of\s+your\s+(session|5-hour|weekly)\s+(?:usage\s+)?limit/i.exec(a);if(!n&&!r&&!t)return null;let o={at:e};if(n){o.sessionPct=Math.min(100,Number(n[2]));let s=zi(n[1]);s!==void 0&&(o.sessionResetsAt=e+s)}if(r){o.weekPct=Math.min(100,Number(r[2]));let s=/resets?\s+((?:sun|mon|tue|wed|thu|fri|sat)\w*\s+\d{1,2}(?::\d{2})?\s*(?:am|pm)?)/i.exec(r[1]);s&&(o.weekResetsText=s[1].trim(),o.weekResetsAt=Pi(s[1],e))}return t&&(/weekly/i.test(t[2])?o.weekPct=Math.min(100,Number(t[1])):o.sessionPct=Math.min(100,Number(t[1]))),o}var ze=i=>new Date(i).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"});function xe(i,e){return{...i,sends:i.sends.filter(a=>e-a.at<Ie).slice(-5e3),readings:i.readings.filter(a=>e-a.at<Ie).slice(-50)}}function Sa(i,e){let a=[...i.readings].reverse().find(t=>t.sessionPct!==void 0),n=i.pctPer1k;if(a&&e.sessionPct!==void 0&&a.sessionPct!==void 0&&e.sessionPct>a.sessionPct&&e.at-a.at<pe){let t=i.sends.filter(o=>o.at>a.at&&o.at<=e.at).reduce((o,s)=>o+s.tokens,0);if(t>=5e3){let o=(e.sessionPct-a.sessionPct)/t*1e3;n=n===void 0?o:n*.5+o*.5}}let r=a&&a.sessionPct===e.sessionPct&&a.weekPct===e.weekPct&&e.at-a.at<10*6e4;return{...i,pctPer1k:n,readings:r?i.readings:[...i.readings,e]}}function _a(i){let{data:e,now:a,plan:n,current:r}=i,t=[...e.readings].reverse().find(p=>p.sessionPct!==void 0||p.weekPct!==void 0),o=[...e.readings].reverse().find(p=>p.weekPct!==void 0),s=[...e.readings].reverse().find(p=>p.sessionPct!==void 0),l=s&&(s.sessionResetsAt??s.at+pe)>a,u=l&&s.sessionResetsAt?s.sessionResetsAt-pe:a-pe,d=e.sends.filter(p=>p.at>=u),h=p=>p.reduce((y,A)=>y+A.tokens,0),k=h(d),c={plan:n,planSource:i.planSource,session:{messages:d.length,tokens:k,since:u},week:{messages:e.sends.length,tokens:h(e.sends)},topChats:[],insights:[]};if(l&&s?.sessionPct!==void 0){let p=h(e.sends.filter(A=>A.at>s.at)),y=s.sessionPct+(e.pctPer1k?p/1e3*e.pctPer1k:0);c.sessionPct=Math.min(100,Math.round(y)),c.sessionResetsAt=s.sessionResetsAt,c.meterAgeMin=Math.round((a-s.at)/6e4)}o?.weekPct!==void 0&&(o.weekResetsAt??1/0)>a&&(c.weekPct=o.weekPct,c.weekResetsAt=o.weekResetsAt,c.weekResetsText=o.weekResetsText);let f=new Map;for(let p of d){let y=f.get(p.chat)??{chat:p.chat,title:i.titles?.get(p.chat),tokens:0,messages:0,share:0};y.tokens+=p.tokens,y.messages++,f.set(p.chat,y)}c.topChats=[...f.values()].map(p=>({...p,share:k?p.tokens/k:0})).sort((p,y)=>y.tokens-p.tokens).slice(0,5);let g=Ti,w=(r?.tokens??0)+g,C=Ci+g;if(c.sessionPct!==void 0&&e.pctPer1k){let p=Math.max(0,100-c.sessionPct)/e.pctPer1k*1e3;c.left={here:Math.floor(p/w),fresh:Math.floor(p/C)}}let E=c.insights,S=n?n.label:"your plan";if(c.sessionPct!==void 0){let p=c.sessionResetsAt?`; it resets at ${ze(c.sessionResetsAt)}`:"";c.left&&r&&r.tokens>=1e4&&c.left.fresh>=c.left.here*2?E.push(`About ${re(c.left.here)} more messages fit in ${S}'s session at this chat's size, or about ${re(c.left.fresh)} in a new chat (${c.sessionPct}% used${p}). For a new topic, start a new chat. ${z}`):c.left?E.push(`About ${re(c.left.fresh)} more messages like your recent ones fit in this session (${c.sessionPct}% used${p}).`):E.push(`Session ${c.sessionPct}% used${p}.`);let y=s&&e.readings.find(A=>A.sessionPct!==void 0&&A.at>=u);if(y&&s&&y!==s&&c.sessionResetsAt&&c.sessionPct<100){let A=(c.sessionPct-(y.sessionPct??0))/Math.max(1,a-y.at),O=A>0?a+(100-c.sessionPct)/A:1/0;O<c.sessionResetsAt&&E.push(`At this pace the session runs out around ${ze(O)}, before it resets at ${ze(c.sessionResetsAt)}. Save the big asks for after the reset, or start new topics in new chats.`)}}let T=c.topChats[0];return T&&d.length>=4&&T.share>=.5&&f.size>1&&E.push(`${Math.round(T.share*100)}% of this session's usage came from one chat${T.title?` ("${T.title}")`:""}: each message there re-read about ${re(T.tokens/T.messages)} tokens.`),c.weekPct!==void 0&&c.weekResetsAt&&((c.weekResetsAt-a)/36e5<=36&&c.weekPct<=50?E.push(`${100-c.weekPct}% of ${S}'s weekly allowance is unused and resets ${c.weekResetsText??"soon"}. It doesn't carry over, so this is a good time for the big jobs you've been holding back.`):c.weekPct>=80&&E.push(`Week ${c.weekPct}% used${c.weekResetsText?`, resets ${c.weekResetsText}`:""}. New topics in new chats and one complete message per ask stretch what's left.`)),r&&r.tokens>=2e4&&!E.some(p=>p.includes("new chat"))&&E.push(`Every message in this chat re-reads about ${re(r.tokens)} tokens, ${Math.max(2,Math.round(w/C))}\xD7 a new chat. On ${S} that's what uses your allowance fastest.`),n?c.sessionPct===void 0&&n.site==="claude"&&E.push("Open Settings > Usage in Claude once and Token Lens reads your meter from it, to count how many messages you have left."):E.push("Tell Token Lens your plan once (Free, Pro, Max or Team) for advice that fits it."),c}var Ii=/^(?:ok(?:ay)?|k|thanks?(?: you)?(?: so much)?|thx|ty|great|perfect|nice|cool|awesome|got it|understood|sounds good|lgtm|yes|yep|sure|done|👍|🙏)[\s!.,🙂😊👍🙏]*$/i;function Ea(i){return Ii.test(i.trim())}var Me=i=>i.toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").trim();function Aa(i,e){let a=Me(i);return a.split(" ").length<8?!1:Me(e).includes(a)}function Ca(i){return i.split(/\n\s*\n/).map(e=>e.trim()).filter(e=>e.length>=1200).map(e=>ge(Me(e)))}function he(i){let e=typeof i.model=="string"?K(i.model,i.site):i.model,a=i.pricing?{...e,pricing:i.pricing}:e,n=le(i.prompt),{tokens:r,exact:t}=_(i.prompt,a),o=$(i.conversationTokens??0,r,a,i.contextWindow),s=De(i.level??"auto",o),l=V(i.prompt,{profile:a,task:n.task,level:s}),u=fa(l.text,a),d=u.saved>0&&u.blocks.length?{...l,text:u.text,tokens:u.after,saved:l.originalTokens-u.after,rules:[...l.rules,"JSON whitespace (same data)"]}:l,h=fe({profile:a,task:n.task,signals:n.signals,promptTokens:r,usage:o,text:i.prompt}),k={pricing:a.pricing,promptUsd:N(r,a),sendUsd:N((i.conversationTokens??0)+r,a),saved:W(d.saved,a)},c=Object.entries(Re(i.prompt,d.text)).map(([g,[w,C]])=>`${C}/${w} ${g}s`).join(", "),f={text:d.text,tokens:d.tokens,changes:d.saved>0?d.rules.map(g=>`removed ${g}`):[],kept:c||"every instruction"};return{profile:a,task:n,promptTokens:r,exact:t,usage:o,level:s,optimized:d,tips:h,handoffPrompt:Ne(n.task),cost:k,best:f}}var ji=60,Di=20,Ni=12,Li=1800,Oi=25e3,Bi=15e3,Fi=80,Gi=3*6e4,J=i=>Math.round(i).toLocaleString("en-US"),Ta=i=>i.toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").trim(),be=class{constructor(e="claude-sonnet",a={}){m(this,"profile");m(this,"modelKnown",!1);m(this,"draftText","");m(this,"draftSince",0);m(this,"draftDone","");m(this,"lastDraftNotice",-1/0);m(this,"chatTold",new Map);m(this,"chatTokens",new Map);m(this,"chatEpoch",new Map);m(this,"chatText","");m(this,"chatId","");m(this,"chatTitle","");m(this,"titles",new Map);m(this,"pending",null);m(this,"pastes",new Map);m(this,"told",new Set);m(this,"data");m(this,"pickedPlan");m(this,"appPlan",null);m(this,"onData");m(this,"state");this.profile=K(e),this.data=xe(a.data??{sends:[],readings:[]},Date.now()),this.pickedPlan=a.plan&&ne[a.plan]||null,this.onData=a.onData,this.state={model:this.profile.label,draft:null,chat:null,coach:null},this.refreshCoach(Date.now())}get currentDraft(){return this.draftText}get plan(){return this.pickedPlan??this.appPlan}useModel(e){let a=K(e);a.id!=="generic"&&(this.profile=a,this.modelKnown=!0,this.state.model=this.profile.label)}setPlan(e,a=Date.now()){return this.pickedPlan=e&&ne[e]||null,this.refreshCoach(a),this.plan}draft(e,a){let n=(e??"").trim();if(n!==this.draftText)return e===""&&this.draftText.length>2&&this.chatId?this.pending={probe:Ta(this.draftText).slice(0,80),tokens:this.state.chat?.tokens??0,chat:this.chatId,at:a,text:this.draftText}:this.pending&&n===this.pending.text&&(this.pending=null),this.draftText=n,this.draftSince=a,this.state.draft=n?this.analyzeDraft(n):null,null;let r=this.state.draft;if(!r||a-this.draftSince<Li)return null;let t=ge(n);if(t===this.draftDone||a-this.lastDraftNotice<Oi)return null;let o=this.draftNotice(n,r);return o?(this.draftDone=t,this.lastDraftNotice=a,o):null}draftNotice(e,a){let n=this.state.chat?.tokens??0,r=this.plan?.label??"your plan";if(Aa(e,this.chatText))return{title:"You already asked this in this chat",body:`Its answer is above. Sending it again re-reads about ${J(n+a.tokens)} tokens of ${r}'s usage for the same question.`};if(Ea(e)&&n>=Bi)return{title:`"${e.slice(0,24)}" re-reads this whole chat`,body:`Sending it uses about ${J(n)} tokens of ${r}'s usage, as much as a long question. If there's nothing to ask, you can skip it, or add it to your next real message.`};let t=Ca(e);for(let s of t){let l=this.pastes.get(s)??new Set;if(l.add(this.chatId),this.pastes.set(s,l),l.size>=2&&!this.told.has(`paste:${s}`))return this.told.add(`paste:${s}`),{title:`You've pasted this text into ${l.size} chats`,body:"Put it in a Claude Project's knowledge or instructions once, and every chat in that Project has it without pasting it again."}}return a.tokens>=ji&&a.saved>=Di&&a.pct>=Ni?{title:`Draft: ${J(a.tokens)} \u2192 ${J(a.optimizedTokens)} tokens (${a.pct}% fewer)`,body:`Same request, ${a.savedUsd===null?"fewer tokens":`${Pe(a.savedUsd)} less per send at API prices`}. Token Lens can put the shorter version in the message box when you click.${a.tips[0]?` ${a.tips[0]}`:""}`}:a.tips.length?{title:"Token Lens tip for your draft",body:a.tips[0]}:null}chat(e,a,n=Date.now()){let{tokens:r}=_(a,this.profile),t=$(r,0,this.profile),o=N(r,this.profile),s=[],l=null;t.zone==="hot"||t.zone==="full"?(l=t.zone,s.push(`This chat fills about ${Math.round(t.ratio*100)}% of the context, so older messages will soon be dropped or compacted. ${z}`)):(t.zone==="warm"||t.longChat)&&(l=t.zone==="warm"?"warm":"long",s.push(`Every message you send re-reads about ${J(r)} tokens of this chat (${Pe(o)} each at API prices). Starting a new chat for a new topic avoids that.`)),this.state.chat={tokens:r,ratio:t.ratio,window:t.window,perMessageUsd:o,tips:s},r<(this.chatTokens.get(e)??0)*.5&&(this.chatTold.delete(e),this.chatEpoch.set(e,(this.chatEpoch.get(e)??0)+1)),this.chatTokens.set(e,r),this.chatText=a,this.chatId=ge(`${e}#${this.chatEpoch.get(e)??0}`),this.chatTitle=e&&!/^claude$/i.test(e)?e.slice(0,60):"",this.chatTitle&&this.titles.set(this.chatId,this.chatTitle);let u=this.observe(a,n),d=null;if(l){let h=this.chatTold.get(e)??new Set;this.chatTold.set(e,h),h.has(l)||(h.add(l),d={title:`This chat: ${J(r)} tokens (${Math.round(t.ratio*100)}% of context)`,body:s[0]})}return u??d}observe(e,a){let n=!1,r=ya(e);r&&(this.appPlan=r);let t=wa(e,a);t&&(this.data=Sa(this.data,t),n=!0);let o=this.pending;o&&a-o.at>Gi?this.pending=null:o&&o.probe&&Ta(e).includes(o.probe)&&(this.data.sends.push({at:o.at,tokens:o.tokens+_(o.text,this.profile).tokens,chat:o.chat}),this.pending=null,n=!0),n&&(this.data=xe(this.data,a),this.onData?.(this.data));let s=this.refreshCoach(a);if(s.sessionPct!==void 0&&s.sessionPct>=Fi){let u=`session:${Math.round(s.session.since/36e5)}`;if(!this.told.has(u))return this.told.add(u),{title:`${this.plan?.label??"Claude"} session ${s.sessionPct}% used`,body:s.insights[0]??"New topics in new chats stretch what's left."}}let l=s.insights.find(u=>u.includes("weekly allowance is unused"));if(l&&s.weekResetsAt){let u=`week:${Math.round(s.weekResetsAt/864e5)}`;if(!this.told.has(u))return this.told.add(u),{title:`${100-(s.weekPct??0)}% of your week is still unused`,body:l}}return null}refreshCoach(e=Date.now()){let a=_a({data:this.data,now:e,plan:this.plan,planSource:this.pickedPlan?"set":this.appPlan?"app":"unknown",current:this.chatId?{chat:this.chatId,title:this.chatTitle,tokens:this.state.chat?.tokens??0}:void 0,titles:this.titles});return this.state.coach=a,a}analyzeDraft(e){let a=he({prompt:e,model:this.profile,conversationTokens:this.state.chat?.tokens??0,level:"auto"}),n=a.optimized.saved;return{tokens:a.promptTokens,optimizedTokens:a.optimized.tokens,saved:n,pct:a.optimized.originalTokens?Math.round(n/a.optimized.originalTokens*100):0,savedUsd:a.cost.saved.usd,best:n>0?a.best.text:e,kept:a.best.kept,tips:a.tips.filter(r=>!["handoff","price-threshold","family-note"].includes(r.id)).map(r=>r.text)}}};var L=null,je="",qi={version:"0.2.54",start(i,e,a){let n;try{n=e?JSON.parse(e):void 0,(!n||!Array.isArray(n.sends)||!Array.isArray(n.readings))&&(n=void 0)}catch{n=void 0}L=new be(i||"claude-sonnet",{data:n,plan:a||void 0,onData:r=>je=JSON.stringify(r)})},draft(i,e){let a=L?.draft(i,e);return a?JSON.stringify(a):""},chat(i,e,a){let n=L?.chat(i,e,a);return n?JSON.stringify(n):""},state(i){return L?(L.refreshCoach(i),JSON.stringify(L.state)):"{}"},currentDraft(){return L?.currentDraft??""},setPlan(i){L?.setPlan(i||null)},takeData(){let i=je;return je="",i},optimize(i,e){let a=he({prompt:i,model:e,level:"auto"});return JSON.stringify({best:a.best.text,tokens:a.promptTokens,optimizedTokens:a.optimized.tokens,kept:a.best.kept})}};globalThis.TokenLensCore=qi;})();
|
|
3336
|
+
`);return E!==C&&(h=!0),C=E,C});return n.length&&r.push(`removed indentation and line breaks from ${n.length} JSON block${n.length>1?"s":""} (same data)`),h&&r.push("removed trailing spaces and extra blank lines outside code"),o(k.join(""),n.length||h?void 0:"Nothing to compact: no pretty-printed JSON or extra whitespace found.")}var v=i=>i,G={"gpt-astra":v({id:"gpt-astra",label:"GPT-6 Astra",family:"openai",contextWindow:105e4,tokenizer:"heuristic",charsPerToken:4.2,reasoning:!0,pricing:{inputPerMTok:10,outputPerMTok:50}}),"gpt-reasoning":v({id:"gpt-reasoning",label:"GPT (thinking)",family:"openai",contextWindow:196e3,tokenizer:"o200k",charsPerToken:4.2,reasoning:!0,lighterAlternative:"the Instant / non-thinking model",pricing:{inputPerMTok:5,outputPerMTok:30,cachedInputPerMTok:.5}}),gpt:v({id:"gpt",label:"GPT",family:"openai",contextWindow:128e3,tokenizer:"o200k",charsPerToken:4.2,reasoning:!1,pricing:{inputPerMTok:2.5,outputPerMTok:15,cachedInputPerMTok:.25}}),"claude-fable":v({id:"claude-fable",label:"Claude Fable",family:"anthropic",contextWindow:1e6,tokenizer:"heuristic",charsPerToken:3.5,reasoning:!0,notes:["Claude reads XML-style tags well: wrap pasted material in <doc>\u2026</doc> instead of explaining it."],pricing:{inputPerMTok:10,outputPerMTok:50,cachedInputPerMTok:.25}}),"claude-opus":v({id:"claude-opus",label:"Claude Opus",family:"anthropic",contextWindow:2e5,tokenizer:"heuristic",charsPerToken:3.5,reasoning:!1,lighterAlternative:"Sonnet or Haiku",notes:["Claude reads XML-style tags well: wrap pasted material in <doc>\u2026</doc> instead of explaining it."],pricing:{inputPerMTok:4,outputPerMTok:20,cachedInputPerMTok:.2}}),"claude-sonnet":v({id:"claude-sonnet",label:"Claude Sonnet",family:"anthropic",contextWindow:2e5,tokenizer:"heuristic",charsPerToken:3.5,reasoning:!1,lighterAlternative:"Haiku",notes:["Claude reads XML-style tags well: wrap pasted material in <doc>\u2026</doc> instead of explaining it."],pricing:{inputPerMTok:2,outputPerMTok:10,cachedInputPerMTok:.2}}),"claude-haiku":v({id:"claude-haiku",label:"Claude Haiku",family:"anthropic",contextWindow:2e5,tokenizer:"heuristic",charsPerToken:3.5,reasoning:!1,pricing:{inputPerMTok:.1,outputPerMTok:.5,cachedInputPerMTok:.01}}),"gemini-pro":v({id:"gemini-pro",label:"Gemini Pro",family:"google",contextWindow:1e6,tokenizer:"heuristic",charsPerToken:4,reasoning:!0,lighterAlternative:"Flash",pricing:{inputPerMTok:2,outputPerMTok:12,cachedInputPerMTok:.2,longContext:{aboveTokens:2e5,inputPerMTok:4,outputPerMTok:18}}}),"gemini-flash":v({id:"gemini-flash",label:"Gemini Flash",family:"google",contextWindow:1e6,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:1.5,outputPerMTok:9,cachedInputPerMTok:.15}}),deepseek:v({id:"deepseek",label:"DeepSeek",family:"deepseek",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:.28,outputPerMTok:.42,cachedInputPerMTok:.028}}),"deepseek-reasoning":v({id:"deepseek-reasoning",label:"DeepSeek (thinking)",family:"deepseek",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!0,lighterAlternative:"the non-thinking mode",pricing:{inputPerMTok:.55,outputPerMTok:2.19,cachedInputPerMTok:.14}}),mistral:v({id:"mistral",label:"Mistral",family:"mistral",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:2,outputPerMTok:6}}),grok:v({id:"grok",label:"Grok",family:"xai",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:2,outputPerMTok:6}}),llama:v({id:"llama",label:"Llama",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.9,reasoning:!1,pricing:{inputPerMTok:.27,outputPerMTok:.85}}),qwen:v({id:"qwen",label:"Qwen",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:.4,outputPerMTok:1.2}}),kimi:v({id:"kimi",label:"Kimi",family:"other",contextWindow:256e3,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:.6,outputPerMTok:2.5,cachedInputPerMTok:.15}}),glm:v({id:"glm",label:"GLM",family:"other",contextWindow:2e5,tokenizer:"heuristic",charsPerToken:3.8,reasoning:!1,pricing:{inputPerMTok:.6,outputPerMTok:2.2,cachedInputPerMTok:.11}}),command:v({id:"command",label:"Cohere Command",family:"other",contextWindow:256e3,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:2.5,outputPerMTok:10}}),sonar:v({id:"sonar",label:"Perplexity Sonar",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:1,outputPerMTok:1}}),gemma:v({id:"gemma",label:"Gemma",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:3.9,reasoning:!1,pricing:{inputPerMTok:.1,outputPerMTok:.2}}),phi:v({id:"phi",label:"Phi",family:"other",contextWindow:16e3,tokenizer:"heuristic",charsPerToken:3.9,reasoning:!1,pricing:{inputPerMTok:.07,outputPerMTok:.14}}),generic:v({id:"generic",label:"Unknown model",family:"other",contextWindow:128e3,tokenizer:"heuristic",charsPerToken:4,reasoning:!1,pricing:{inputPerMTok:2,outputPerMTok:10}})},_i=[[/\b(fable|mythos)\b/,"claude-fable"],[/\bastra\b|\bgpt[- ]?6\b/,"gpt-astra"],[/\bclaude\b.*\bopus\b|\bopus\b/,"claude-opus"],[/\bsonnet\b/,"claude-sonnet"],[/\bhaiku\b/,"claude-haiku"],[/\b(qwen|qwq)/,"qwen"],[/\b(glm|chatglm|zhipu)\b|\bglm-?\d/,"glm"],[/\b(kimi|moonshot)/,"kimi"],[/\bgemma/,"gemma"],[/\bgemini\b.*\bflash\b|\bflash\b/,"gemini-flash"],[/\bgemini\b.*\b(pro|ultra|deep think)\b|^\s*\d(\.\d)?\s*pro\b|\bdeep think\b/,"gemini-pro"],[/\bdeepseek\b.*\b(r1|reason|think|deepthink)|\bdeepthink\b/,"deepseek-reasoning"],[/\bdeepseek\b/,"deepseek"],[/\b(mistral|le chat|magistral|codestral)\b/,"mistral"],[/\bgrok\b/,"grok"],[/\bllama|codellama/,"llama"],[/\bcommand[- ]?(r|a)\b|\bcohere\b/,"command"],[/\bsonar\b|\bperplexity\b/,"sonar"],[/\bphi-?\d/,"phi"],[/\bo[1-9](-mini|-pro)?\b|\bgpt[- ]?\S*\s*(thinking|pro)\b|\bthinking\b/,"gpt-reasoning"],[/\b(gpt|chatgpt)\b|\bgpt-?\d/,"gpt"],[/\bgemini\b/,"gemini-pro"],[/\bclaude\b/,"claude-sonnet"]];var Ei={chatgpt:"gpt",claude:"claude-sonnet",gemini:"gemini-flash",deepseek:"deepseek",mistral:"mistral",grok:"grok",generic:"generic"},Ai=[];var zr={...G};function K(i,e="generic"){let a=(i??"").toLowerCase().replace(/\s+/g," ").trim();if(a){for(let[n,r]of Ai)if(n.test(a))return G[r];if(e==="gemini"&&/\b(pro|deep think)\b/.test(a))return G["gemini-pro"];if(e==="gemini"&&/\bflash\b/.test(a))return G["gemini-flash"];if(e==="deepseek"&&/\b(deepthink|r1|think)/.test(a))return G["deepseek-reasoning"];for(let[n,r]of _i)if(n.test(a))return G[r]}return G[Ei[e]??"generic"]}var va="ai-toolbox.co ChatGPT limits guide",ne={"chatgpt-free":{id:"chatgpt-free",site:"chatgpt",label:"ChatGPT Free",meter:"messages",caps:[]},"chatgpt-go":{id:"chatgpt-go",site:"chatgpt",label:"ChatGPT Go",meter:"messages",caps:[]},"chatgpt-plus":{id:"chatgpt-plus",site:"chatgpt",label:"ChatGPT Plus",meter:"messages",caps:[{models:["gpt"],messages:160,window:"3h",source:va,asOf:"2026-10"},{models:["gpt-reasoning"],messages:3e3,window:"week",source:va,asOf:"2026-10"}]},"chatgpt-pro":{id:"chatgpt-pro",site:"chatgpt",label:"ChatGPT Pro",meter:"messages",caps:[],note:"Pro is close to unlimited for everyday models, but GPT-6 Astra Pro use is capped (number not published)."},"chatgpt-business":{id:"chatgpt-business",site:"chatgpt",label:"ChatGPT Business",meter:"messages",caps:[]},"claude-free":{id:"claude-free",site:"claude",label:"Claude Free",meter:"compute",caps:[]},"claude-pro":{id:"claude-pro",site:"claude",label:"Claude Pro",meter:"compute",caps:[],note:"5-hour session and weekly limits shared across models; Fable runs on usage credits."},"claude-max":{id:"claude-max",site:"claude",label:"Claude Max",meter:"compute",caps:[],note:"5x or 20x Pro's session usage, with a weekly limit across models and one for the top models."},"claude-team":{id:"claude-team",site:"claude",label:"Claude Team",meter:"compute",caps:[]},"gemini-free":{id:"gemini-free",site:"gemini",label:"Gemini (free)",meter:"messages",caps:[]},"gemini-pro":{id:"gemini-pro",site:"gemini",label:"Google AI Pro",meter:"messages",caps:[]},"gemini-ultra":{id:"gemini-ultra",site:"gemini",label:"Google AI Ultra",meter:"messages",caps:[]}};var pe=5*36e5,Ie=7*864e5,Ci=1e3,Ti=500,re=i=>Math.round(i).toLocaleString("en-US");function ge(i){let e=2166136261;for(let a=0;a<i.length;a++)e=Math.imul(e^i.charCodeAt(a),16777619)>>>0;return e.toString(36)}function ya(i){let e=/\b(Free|Pro|Max|Team|Enterprise)\s+plan\b/.exec(i);if(!e)return null;let a=e[1].toLowerCase();return ne[`claude-${a==="enterprise"?"team":a}`]??null}var Ri=["sun","mon","tue","wed","thu","fri","sat"];function Pi(i,e){let a=/\b(sun|mon|tue|wed|thu|fri|sat)\w*\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/i.exec(i);if(!a)return;let n=new Date(e),r=Number(a[2])%12;a[4]?.toLowerCase()==="pm"&&(r+=12),!a[4]&&Number(a[2])>=12&&(r=Number(a[2]));let t=(Ri.indexOf(a[1].slice(0,3).toLowerCase())-n.getDay()+7)%7,o=new Date(n.getFullYear(),n.getMonth(),n.getDate()+t,r,Number(a[3]??0)).getTime();return o<=e?o+Ie:o}function zi(i){let e=/resets?\s+in\s+(?:(\d+)\s*(?:hr|hour|h)s?)?\s*(?:(\d+)\s*(?:min|minute|m)s?)?/i.exec(i);if(!(!e||!e[1]&&!e[2]))return(Number(e[1]??0)*60+Number(e[2]??0))*6e4}function wa(i,e){let a=i.replace(/\s+/g," "),n=/current session(.{0,160}?)(\d{1,3})\s*%\s*used/i.exec(a),r=/weekly (?:limits?|usage)(.{0,260}?)(\d{1,3})\s*%\s*used/i.exec(a),t=!n&&/(\d{1,3})\s*%\s+of\s+your\s+(session|5-hour|weekly)\s+(?:usage\s+)?limit/i.exec(a);if(!n&&!r&&!t)return null;let o={at:e};if(n){o.sessionPct=Math.min(100,Number(n[2]));let s=zi(n[1]);s!==void 0&&(o.sessionResetsAt=e+s)}if(r){o.weekPct=Math.min(100,Number(r[2]));let s=/resets?\s+((?:sun|mon|tue|wed|thu|fri|sat)\w*\s+\d{1,2}(?::\d{2})?\s*(?:am|pm)?)/i.exec(r[1]);s&&(o.weekResetsText=s[1].trim(),o.weekResetsAt=Pi(s[1],e))}return t&&(/weekly/i.test(t[2])?o.weekPct=Math.min(100,Number(t[1])):o.sessionPct=Math.min(100,Number(t[1]))),o}var ze=i=>new Date(i).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"});function xe(i,e){return{...i,sends:i.sends.filter(a=>e-a.at<Ie).slice(-5e3),readings:i.readings.filter(a=>e-a.at<Ie).slice(-50)}}function Sa(i,e){let a=[...i.readings].reverse().find(t=>t.sessionPct!==void 0),n=i.pctPer1k;if(a&&e.sessionPct!==void 0&&a.sessionPct!==void 0&&e.sessionPct>a.sessionPct&&e.at-a.at<pe){let t=i.sends.filter(o=>o.at>a.at&&o.at<=e.at).reduce((o,s)=>o+s.tokens,0);if(t>=5e3){let o=(e.sessionPct-a.sessionPct)/t*1e3;n=n===void 0?o:n*.5+o*.5}}let r=a&&a.sessionPct===e.sessionPct&&a.weekPct===e.weekPct&&e.at-a.at<10*6e4;return{...i,pctPer1k:n,readings:r?i.readings:[...i.readings,e]}}function _a(i){let{data:e,now:a,plan:n,current:r}=i,t=[...e.readings].reverse().find(p=>p.sessionPct!==void 0||p.weekPct!==void 0),o=[...e.readings].reverse().find(p=>p.weekPct!==void 0),s=[...e.readings].reverse().find(p=>p.sessionPct!==void 0),l=s&&(s.sessionResetsAt??s.at+pe)>a,u=l&&s.sessionResetsAt?s.sessionResetsAt-pe:a-pe,d=e.sends.filter(p=>p.at>=u),h=p=>p.reduce((y,A)=>y+A.tokens,0),k=h(d),c={plan:n,planSource:i.planSource,session:{messages:d.length,tokens:k,since:u},week:{messages:e.sends.length,tokens:h(e.sends)},topChats:[],insights:[]};if(l&&s?.sessionPct!==void 0){let p=h(e.sends.filter(A=>A.at>s.at)),y=s.sessionPct+(e.pctPer1k?p/1e3*e.pctPer1k:0);c.sessionPct=Math.min(100,Math.round(y)),c.sessionResetsAt=s.sessionResetsAt,c.meterAgeMin=Math.round((a-s.at)/6e4)}o?.weekPct!==void 0&&(o.weekResetsAt??1/0)>a&&(c.weekPct=o.weekPct,c.weekResetsAt=o.weekResetsAt,c.weekResetsText=o.weekResetsText);let f=new Map;for(let p of d){let y=f.get(p.chat)??{chat:p.chat,title:i.titles?.get(p.chat),tokens:0,messages:0,share:0};y.tokens+=p.tokens,y.messages++,f.set(p.chat,y)}c.topChats=[...f.values()].map(p=>({...p,share:k?p.tokens/k:0})).sort((p,y)=>y.tokens-p.tokens).slice(0,5);let g=Ti,w=(r?.tokens??0)+g,C=Ci+g;if(c.sessionPct!==void 0&&e.pctPer1k){let p=Math.max(0,100-c.sessionPct)/e.pctPer1k*1e3;c.left={here:Math.floor(p/w),fresh:Math.floor(p/C)}}let E=c.insights,S=n?n.label:"your plan";if(c.sessionPct!==void 0){let p=c.sessionResetsAt?`; it resets at ${ze(c.sessionResetsAt)}`:"";c.left&&r&&r.tokens>=1e4&&c.left.fresh>=c.left.here*2?E.push(`About ${re(c.left.here)} more messages fit in ${S}'s session at this chat's size, or about ${re(c.left.fresh)} in a new chat (${c.sessionPct}% used${p}). For a new topic, start a new chat. ${z}`):c.left?E.push(`About ${re(c.left.fresh)} more messages like your recent ones fit in this session (${c.sessionPct}% used${p}).`):E.push(`Session ${c.sessionPct}% used${p}.`);let y=s&&e.readings.find(A=>A.sessionPct!==void 0&&A.at>=u);if(y&&s&&y!==s&&c.sessionResetsAt&&c.sessionPct<100){let A=(c.sessionPct-(y.sessionPct??0))/Math.max(1,a-y.at),O=A>0?a+(100-c.sessionPct)/A:1/0;O<c.sessionResetsAt&&E.push(`At this pace the session runs out around ${ze(O)}, before it resets at ${ze(c.sessionResetsAt)}. Save the big asks for after the reset, or start new topics in new chats.`)}}let T=c.topChats[0];return T&&d.length>=4&&T.share>=.5&&f.size>1&&E.push(`${Math.round(T.share*100)}% of this session's usage came from one chat${T.title?` ("${T.title}")`:""}: each message there re-read about ${re(T.tokens/T.messages)} tokens.`),c.weekPct!==void 0&&c.weekResetsAt&&((c.weekResetsAt-a)/36e5<=36&&c.weekPct<=50?E.push(`${100-c.weekPct}% of ${S}'s weekly allowance is unused and resets ${c.weekResetsText??"soon"}. It doesn't carry over, so this is a good time for the big jobs you've been holding back.`):c.weekPct>=80&&E.push(`Week ${c.weekPct}% used${c.weekResetsText?`, resets ${c.weekResetsText}`:""}. New topics in new chats and one complete message per ask stretch what's left.`)),r&&r.tokens>=2e4&&!E.some(p=>p.includes("new chat"))&&E.push(`Every message in this chat re-reads about ${re(r.tokens)} tokens, ${Math.max(2,Math.round(w/C))}\xD7 a new chat. On ${S} that's what uses your allowance fastest.`),n?c.sessionPct===void 0&&n.site==="claude"&&E.push("Open Settings > Usage in Claude once and Token Lens reads your meter from it, to count how many messages you have left."):E.push("Tell Token Lens your plan once (Free, Pro, Max or Team) for advice that fits it."),c}var Ii=/^(?:ok(?:ay)?|k|thanks?(?: you)?(?: so much)?|thx|ty|great|perfect|nice|cool|awesome|got it|understood|sounds good|lgtm|yes|yep|sure|done|👍|🙏)[\s!.,🙂😊👍🙏]*$/i;function Ea(i){return Ii.test(i.trim())}var Me=i=>i.toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").trim();function Aa(i,e){let a=Me(i);return a.split(" ").length<8?!1:Me(e).includes(a)}function Ca(i){return i.split(/\n\s*\n/).map(e=>e.trim()).filter(e=>e.length>=1200).map(e=>ge(Me(e)))}function he(i){let e=typeof i.model=="string"?K(i.model,i.site):i.model,a=i.pricing?{...e,pricing:i.pricing}:e,n=le(i.prompt),{tokens:r,exact:t}=_(i.prompt,a),o=$(i.conversationTokens??0,r,a,i.contextWindow),s=De(i.level??"auto",o),l=V(i.prompt,{profile:a,task:n.task,level:s}),u=fa(l.text,a),d=u.saved>0&&u.blocks.length?{...l,text:u.text,tokens:u.after,saved:l.originalTokens-u.after,rules:[...l.rules,"JSON whitespace (same data)"]}:l,h=fe({profile:a,task:n.task,signals:n.signals,promptTokens:r,usage:o,text:i.prompt}),k={pricing:a.pricing,promptUsd:N(r,a),sendUsd:N((i.conversationTokens??0)+r,a),saved:W(d.saved,a)},c=Object.entries(Re(i.prompt,d.text)).map(([g,[w,C]])=>`${C}/${w} ${g}s`).join(", "),f={text:d.text,tokens:d.tokens,changes:d.saved>0?d.rules.map(g=>`removed ${g}`):[],kept:c||"every instruction"};return{profile:a,task:n,promptTokens:r,exact:t,usage:o,level:s,optimized:d,tips:h,handoffPrompt:Ne(n.task),cost:k,best:f}}var ji=60,Di=20,Ni=12,Li=1800,Oi=25e3,Bi=15e3,Fi=80,Gi=3*6e4,J=i=>Math.round(i).toLocaleString("en-US"),Ta=i=>i.toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").trim(),be=class{constructor(e="claude-sonnet",a={}){m(this,"profile");m(this,"modelKnown",!1);m(this,"draftText","");m(this,"draftSince",0);m(this,"draftDone","");m(this,"lastDraftNotice",-1/0);m(this,"chatTold",new Map);m(this,"chatTokens",new Map);m(this,"chatEpoch",new Map);m(this,"chatText","");m(this,"chatId","");m(this,"chatTitle","");m(this,"titles",new Map);m(this,"pending",null);m(this,"pastes",new Map);m(this,"told",new Set);m(this,"data");m(this,"pickedPlan");m(this,"appPlan",null);m(this,"onData");m(this,"state");this.profile=K(e),this.data=xe(a.data??{sends:[],readings:[]},Date.now()),this.pickedPlan=a.plan&&ne[a.plan]||null,this.onData=a.onData,this.state={model:this.profile.label,draft:null,chat:null,coach:null},this.refreshCoach(Date.now())}get currentDraft(){return this.draftText}get plan(){return this.pickedPlan??this.appPlan}useModel(e){let a=K(e);a.id!=="generic"&&(this.profile=a,this.modelKnown=!0,this.state.model=this.profile.label)}setPlan(e,a=Date.now()){return this.pickedPlan=e&&ne[e]||null,this.refreshCoach(a),this.plan}draft(e,a){let n=(e??"").trim();if(n!==this.draftText)return e===""&&this.draftText.length>2&&this.chatId?this.pending={probe:Ta(this.draftText).slice(0,80),tokens:this.state.chat?.tokens??0,chat:this.chatId,at:a,text:this.draftText}:this.pending&&n===this.pending.text&&(this.pending=null),this.draftText=n,this.draftSince=a,this.state.draft=n?this.analyzeDraft(n):null,null;let r=this.state.draft;if(!r||a-this.draftSince<Li)return null;let t=ge(n);if(t===this.draftDone||a-this.lastDraftNotice<Oi)return null;let o=this.draftNotice(n,r);return o?(this.draftDone=t,this.lastDraftNotice=a,o):null}draftNotice(e,a){let n=this.state.chat?.tokens??0,r=this.plan?.label??"your plan";if(Aa(e,this.chatText))return{title:"You already asked this in this chat",body:`Its answer is above. Sending it again re-reads about ${J(n+a.tokens)} tokens of ${r}'s usage for the same question.`};if(Ea(e)&&n>=Bi)return{title:`"${e.slice(0,24)}" re-reads this whole chat`,body:`Sending it uses about ${J(n)} tokens of ${r}'s usage, as much as a long question. If there's nothing to ask, you can skip it, or add it to your next real message.`};let t=Ca(e);for(let s of t){let l=this.pastes.get(s)??new Set;if(l.add(this.chatId),this.pastes.set(s,l),l.size>=2&&!this.told.has(`paste:${s}`))return this.told.add(`paste:${s}`),{title:`You've pasted this text into ${l.size} chats`,body:"Put it in a Claude Project's knowledge or instructions once, and every chat in that Project has it without pasting it again."}}return a.tokens>=ji&&a.saved>=Di&&a.pct>=Ni?{title:`Draft: ${J(a.tokens)} \u2192 ${J(a.optimizedTokens)} tokens (${a.pct}% fewer)`,body:`Same request, ${a.savedUsd===null?"fewer tokens":`${Pe(a.savedUsd)} less per send at API prices`}. Token Lens can put the shorter version in the message box when you click.${a.tips[0]?` ${a.tips[0]}`:""}`}:a.tips.length?{title:"Token Lens tip for your draft",body:a.tips[0]}:null}chat(e,a,n=Date.now()){let{tokens:r}=_(a,this.profile),t=$(r,0,this.profile),o=N(r,this.profile),s=[],l=null;t.zone==="hot"||t.zone==="full"?(l=t.zone,s.push(`This chat fills about ${Math.round(t.ratio*100)}% of the context, so older messages will soon be dropped or compacted. ${z}`)):(t.zone==="warm"||t.longChat)&&(l=t.zone==="warm"?"warm":"long",s.push(`Every message you send re-reads about ${J(r)} tokens of this chat (${Pe(o)} each at API prices). Starting a new chat for a new topic avoids that.`)),this.state.chat={tokens:r,ratio:t.ratio,window:t.window,perMessageUsd:o,tips:s},r<(this.chatTokens.get(e)??0)*.5&&(this.chatTold.delete(e),this.chatEpoch.set(e,(this.chatEpoch.get(e)??0)+1)),this.chatTokens.set(e,r),this.chatText=a,this.chatId=ge(`${e}#${this.chatEpoch.get(e)??0}`),this.chatTitle=e&&!/^claude$/i.test(e)?e.slice(0,60):"",this.chatTitle&&this.titles.set(this.chatId,this.chatTitle);let u=this.observe(a,n),d=null;if(l){let h=this.chatTold.get(e)??new Set;this.chatTold.set(e,h),h.has(l)||(h.add(l),d={title:`This chat: ${J(r)} tokens (${Math.round(t.ratio*100)}% of context)`,body:s[0]})}return u??d}observe(e,a){let n=!1,r=ya(e);r&&(this.appPlan=r);let t=wa(e,a);t&&(this.data=Sa(this.data,t),n=!0);let o=this.pending;o&&a-o.at>Gi?this.pending=null:o&&o.probe&&Ta(e).includes(o.probe)&&(this.data.sends.push({at:o.at,tokens:o.tokens+_(o.text,this.profile).tokens,chat:o.chat}),this.pending=null,n=!0),n&&(this.data=xe(this.data,a),this.onData?.(this.data));let s=this.refreshCoach(a);if(s.sessionPct!==void 0&&s.sessionPct>=Fi){let u=`session:${Math.round(s.session.since/36e5)}`;if(!this.told.has(u))return this.told.add(u),{title:`${this.plan?.label??"Claude"} session ${s.sessionPct}% used`,body:s.insights[0]??"New topics in new chats stretch what's left."}}let l=s.insights.find(u=>u.includes("weekly allowance is unused"));if(l&&s.weekResetsAt){let u=`week:${Math.round(s.weekResetsAt/864e5)}`;if(!this.told.has(u))return this.told.add(u),{title:`${100-(s.weekPct??0)}% of your week is still unused`,body:l}}return null}refreshCoach(e=Date.now()){let a=_a({data:this.data,now:e,plan:this.plan,planSource:this.pickedPlan?"set":this.appPlan?"app":"unknown",current:this.chatId?{chat:this.chatId,title:this.chatTitle,tokens:this.state.chat?.tokens??0}:void 0,titles:this.titles});return this.state.coach=a,a}analyzeDraft(e){let a=he({prompt:e,model:this.profile,conversationTokens:this.state.chat?.tokens??0,level:"auto"}),n=a.optimized.saved;return{tokens:a.promptTokens,optimizedTokens:a.optimized.tokens,saved:n,pct:a.optimized.originalTokens?Math.round(n/a.optimized.originalTokens*100):0,savedUsd:a.cost.saved.usd,best:n>0?a.best.text:e,kept:a.best.kept,tips:a.tips.filter(r=>!["handoff","price-threshold","family-note"].includes(r.id)).map(r=>r.text)}}};var L=null,je="",qi={version:"0.2.55",start(i,e,a){let n;try{n=e?JSON.parse(e):void 0,(!n||!Array.isArray(n.sends)||!Array.isArray(n.readings))&&(n=void 0)}catch{n=void 0}L=new be(i||"claude-sonnet",{data:n,plan:a||void 0,onData:r=>je=JSON.stringify(r)})},draft(i,e){let a=L?.draft(i,e);return a?JSON.stringify(a):""},chat(i,e,a){let n=L?.chat(i,e,a);return n?JSON.stringify(n):""},state(i){return L?(L.refreshCoach(i),JSON.stringify(L.state)):"{}"},currentDraft(){return L?.currentDraft??""},setPlan(i){L?.setPlan(i||null)},takeData(){let i=je;return je="",i},optimize(i,e){let a=he({prompt:i,model:e,level:"auto"});return JSON.stringify({best:a.best.text,tokens:a.promptTokens,optimizedTokens:a.optimized.tokens,kept:a.best.kept})}};globalThis.TokenLensCore=qi;})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kunal_dutt/token-lens",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.55",
|
|
4
4
|
"description": "Count and cut the tokens your prompts, chats and agents use, adapted to the model and task. MCP server and self-hosted gateway. Collects no data.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
package/server.mjs
CHANGED
|
@@ -3852,7 +3852,7 @@ tick();setInterval(tick,1000);
|
|
|
3852
3852
|
</html>
|
|
3853
3853
|
`;var fq="0.1.0",Yn=H.string().describe('Model in use, e.g. "gpt-5"; yours if unsure.'),uo=H.object({input_per_mtok:H.number().nonnegative(),output_per_mtok:H.number().nonnegative(),cached_input_per_mtok:H.number().nonnegative().optional()}).optional().describe("USD per 1M tokens, only if not the list price.");function Sn(e,t){let r=Bn(e);return t?{...r,pricing:{inputPerMTok:t.input_per_mtok,outputPerMTok:t.output_per_mtok,cachedInputPerMTok:t.cached_input_per_mtok}}:r}var Q=e=>e.toLocaleString("en-US"),ba=e=>`Priced at ${e.label}: ${Dv(e.pricing)}.`;function QP(e,t,r){let a=Yr(e,0,t),n=hr(r).task,i=a.zone==="hot"||a.zone==="full"?`Context is nearly full; older messages will be dropped or compacted soon. A handoff to a new chat avoids that. ${Rt}`:a.longChat||a.zone==="warm"?`Every new message re-reads about ${Q(e)} tokens of history. A handoff to a new chat would cut that. ${Rt}`:"The conversation is still short; no handoff needed.",o=a.zone!=="ok"||a.longChat?$p(n):void 0,s=nt(e,t),l=o?Math.max(0,e-1e3):0,c=nt(l*10,t);return{text:[`${Q(e)} tokens, ${Math.round(a.ratio*100)}% of ${t.label}'s ~${Q(a.window)} token context.`,`Each new message re-reads this history: ${Te(s)} of input per message. ${ba(t)}`,...o?[`Optional: a handoff to a new chat saves about ${Q(l)} tokens (${Te(nt(l,t))}) per message, ${Te(c)} over the next 10 messages.`]:[],i,...o?["","Optional handoff request (can change later answers):",o]:[]].join(`
|
|
3854
3854
|
`),structuredContent:{model:t.label,profile:t.id,tokens:e,ratio:a.ratio,zone:a.zone,window:a.window,usdPerMessage:s,handoffSavesTokensPerMessage:l,handoffSavesUsdOver10Messages:c,handoffPrompt:o??null}}}function bq(e){let t=e.optimized.originalTokens?Math.round(e.optimized.saved/e.optimized.originalTokens*100):0,r=[e.optimized.saved>0?`${Ci(e.cost.saved,"send")} (${t}% of the prompt). Meaning, numbers, code and constraints kept.`:`No saving: the prompt is already tight at the ${e.level} level.`,`${e.profile.label}: ${Q(e.promptTokens)} -> ${Q(e.optimized.tokens)} tokens${e.exact?"":" (estimate)"} (${Te(e.cost.promptUsd)} as sent).${e.usage.conversationTokens?` Context: ${Math.round(e.usage.ratio*100)}% of ~${Q(e.usage.window)} tokens.`:""}`];return e.optimized.saved>0&&r.push(`Kept: ${e.best.kept}.`,"--- best prompt, same answer (paste this) ---",e.best.text,"--- end ---"),e.tips.length&&r.push("Suggestions:",...e.tips.map(a=>`- ${a.text}${a.append?` (add: "${a.append}")`:""}`)),r.join(`
|
|
3855
|
-
`)}function vq(e){let t=e.optimized.originalTokens?Math.round(e.optimized.saved/e.optimized.originalTokens*100):0;return e.optimized.saved>0?`Shown in the Token Lens card with Copy: ${Q(e.promptTokens)} -> ${Q(e.optimized.tokens)} tokens (${t}% fewer). Don't repeat the prompt; reply in one short line.`:"Shown in the Token Lens card: already tight. Reply in one short line."}function yq(e,t){let r=Ce(t,e.profile).tokens+30,a=e.promptTokens+20,n=e.profile.pricing;return{tokens:r+a,usd:n?(r*n.inputPerMTok+a*n.outputPerMTok)/1e6:null}}async function kq(e,t,r){try{let a=typeof process<"u"?process.env:{};if(r==="anthropic"&&a.ANTHROPIC_API_KEY&&/^claude-[\w.-]+$/.test(t)){let n=await fetch("https://api.anthropic.com/v1/messages/count_tokens",{method:"POST",headers:{"x-api-key":a.ANTHROPIC_API_KEY,"anthropic-version":"2023-06-01","content-type":"application/json"},body:JSON.stringify({model:t,messages:[{role:"user",content:e}]}),signal:AbortSignal.timeout(5e3)}),i=await n.json();return n.ok&&typeof i.input_tokens=="number"?{tokens:i.input_tokens,exact:!0}:null}if(r==="google"&&a.GEMINI_API_KEY&&/^gemini-[\w.-]+$/.test(t)){let n=await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${t}:countTokens`,{method:"POST",headers:{"x-goog-api-key":a.GEMINI_API_KEY,"content-type":"application/json"},body:JSON.stringify({contents:[{parts:[{text:e}]}]}),signal:AbortSignal.timeout(5e3)}),i=await n.json();return n.ok&&typeof i.totalTokens=="number"?{tokens:i.totalTokens,exact:!0}:null}}catch{}return null}var _q={shorten:"Shorten my prompt before sending",optimize_prompt:"Optimized prompts",optimize_agent:"Agent fixes (per call)",audit_agent_rules:"Rule-file trims (per turn)",compact_data:"Compacted pasted data",compare_prompts:"Checked hand-shortened prompts"},wq="io.modelcontextprotocol/ui";function po(e,t){let r=e.server,a=r.getClientCapabilities();return a?!!a.extensions?.[wq]||/openai|chatgpt/i.test(r.getClientVersion()?.name??""):!!t}function Sq(e,t){return/code|cursor|vscode|windsurf|zed|cline|continue|jetbrains/i.test(e)?!1:t||/^claude-ai$|openai|chatgpt|le ?chat|mistral|perplexity|grok|gemini/i.test(e)}var Eq=["savings_report","plan_coach","live_tips","token_lens_helper"],xq="token-lens/via",e1=process.env.VERCEL_DEPLOYMENT_ID,a1=e1?gq("sha256").update(`token-lens-box:${e1}`).digest("hex").slice(0,32):hq(),w_={"token-lens/box":a1},i1=H.string().optional().describe("Set by the box itself.");function E_(e,t){return e?._meta?.[xq]==="box"||t?.box===a1}var o1={ui:{visibility:["app"]},"openai/visibility":"private","openai/widgetAccessible":!0},T_="For the Token Lens box only; never call it yourself.";function Tq(e){let t=e.server.getClientVersion()?.name??"";return[[/claude-code/i,"Claude Code"],[/claude/i,"Claude"],[/openai|chatgpt/i,"ChatGPT"],[/le ?chat|mistral/i,"Le Chat"],[/perplexity/i,"Perplexity"],[/grok/i,"Grok"],[/gemini/i,"Gemini"],[/cursor/i,"Cursor"],[/visual studio code|vscode/i,"VS Code"]].find(([a])=>a.test(t))?.[1]??""}var Aq={watching:"Shorten while you type is on: tips show here and as notifications.",starting:"Shorten while you type is starting.","cant-see-box":"Can't read the message box yet: click in it, or quit and reopen Claude.","needs-helper":"Shorten while you type needs Token Lens Helper on this Mac.",helper:"Shorten while you type is on: as you type in the message box, a Shorten button shows just above it when a shorter prompt says the same thing.","claude-closed":"Shorten while you type waits for the Claude app.",off:"Shorten while you type is off.",unsupported:""},Cq={watching:"Tips come as notifications while you type in the message box. Set up Token Lens Helper to get a Shorten button right above the box instead, so one click puts the shorter prompt in.","needs-helper":"To get tips while you type in the message box, set up Token Lens Helper. Then a Shorten button shows right above the box."};function zq(e,t){let r=t&&!["helper","off","unsupported"].includes(e);return{status:e,text:r&&Cq[e]||Aq[e]||"",helperOffer:r}}var s1={token_lens_state:{keep:["box"],description:T_},token_lens:{keep:["model"],description:"Open the Token Lens box, where the user shortens prompts and sees costs without sending them to you. Use when asked to use Token Lens or shorten a prompt."},optimize_prompt:{keep:["prompt","model"],appOnly:!0,description:"Shorten a prompt to fewer tokens with the same meaning (every instruction, number, negation and code kept). Returns the shorter prompt and the savings. Use when asked to shorten or optimize a prompt."},check_conversation:{keep:["model","conversation_tokens"],appOnly:!0,description:"How full this chat's context is and what each new message costs. Pass your estimate of the chat's tokens; never copy the chat in."}};function Iq(e){let t=e.registerTool.bind(e);e.registerTool=(r,a,n)=>{let i=s1[r];if(i)return t(r,{...a,description:i.description,inputSchema:l1(a.inputSchema??{},i.keep),_meta:x_(a._meta,i.appOnly)},n)}}var t1=H.string().describe('Your model, e.g. "gpt-5".');function l1(e,t){return Object.fromEntries(Object.entries(e).filter(([r])=>t.includes(r)).map(([r,a])=>[r,r==="model"?a.isOptional()?t1.optional():t1:a]))}function x_(e,t){if(!t)return e;let r=e?.ui??{};return{...e,ui:{...r,visibility:["app"]},"openai/visibility":"private","openai/widgetAccessible":!0}}var Pq=["savings_report"];function Rq(e){let t=new Map,r=e.registerTool.bind(e);e.registerTool=(a,n,i)=>{let o=r(a,n,i);return t.set(a,{h:o,shape:n.inputSchema??{}}),o},e.server.oninitialized=()=>{if(Sq(e.server.getClientVersion()?.name??"",po(e)))for(let[a,{h:n,shape:i}]of t){let o=s1[a];o?(n.description=o.description,n.inputSchema=H.object(l1(i,o.keep)),n._meta=x_(n._meta,o.appOnly)):Pq.includes(a)?n._meta=x_(n._meta,!0):Eq.includes(a)||(n.enabled=!1)}}}function A_(e={}){let t=new Qm({name:"token-lens",version:fq}),r=e.ledger,a=(n,i,o,s)=>r?.record(n,i.id,o,s);return e.chat?Iq(t):process.env.TOKEN_LENS_TOOLS!=="all"&&Rq(t),e.live&&Oq(t,e.live),Mq(t,e.live),XP(t),t.registerPrompt("shorten",{title:"Shorten my prompt before sending",description:"Paste the prompt you're writing. Token Lens shortens it locally, keeping every instruction, fact, number, code block and constraint, and puts the shorter version in your message box. The long version never reaches the model.",argsSchema:{prompt:H.string().describe("The prompt you want to send, pasted in full."),model:H.string().optional().describe(`The model you'll send it to, e.g. "claude-sonnet-4-5" or "gpt-5". Leave blank for any model.`)}},({prompt:n,model:i})=>{let o=Xs({prompt:n,model:Sn(i??"",void 0),level:"auto"});a("shorten",o.profile,o.optimized.saved,o.cost.saved.usd);let s=o.optimized.originalTokens?Math.round(o.optimized.saved/o.optimized.originalTokens*100):0;return{description:o.optimized.saved>0?`Token Lens: ${Q(o.promptTokens)} -> ${Q(o.optimized.tokens)} tokens (${s}% fewer${o.cost.saved.usd===null?"":`, ${Te(o.cost.saved.usd)} saved`}). Kept: ${o.best.kept}.`:"Token Lens: already tight, sent unchanged.",messages:[{role:"user",content:{type:"text",text:o.optimized.saved>0?o.best.text:n}}]}}),t.registerTool("token_lens",{title:"Open Token Lens",description:"Open the Token Lens box in the chat, where the user shortens prompts without sending them to you. Use when asked to open or use Token Lens with no prompt given.",inputSchema:{model:Yn.optional(),chat_tokens:H.number().int().nonnegative().optional().describe("Rough tokens in this chat so far.")},annotations:{readOnlyHint:!0,openWorldHint:!1},_meta:eg("Opening Token Lens\u2026","Token Lens is open")},async({model:n,chat_tokens:i})=>{let o=Sn(n??"",void 0);n&&e.live?.insights.useModel(n);let s=i?QP(i,o,"").structuredContent:{};return{content:[{type:"text",text:po(t,e.chat)?'Token Lens box shown. Reply only "Open." If the user sees no box, ask for their prompt and call optimize_prompt.':"This app shows no Token Lens box. Ask the user to paste the prompt to shorten, then call optimize_prompt."}],structuredContent:{...s,panel:!0,model:n?o.label:"",profile:o.id},...po(t,e.chat)?{_meta:w_}:{}}}),t.registerTool("token_lens_state",{title:"Token Lens box status",description:T_,inputSchema:{box:i1},annotations:{readOnlyHint:!0,openWorldHint:!1},_meta:o1},async(n,i)=>{let o={app:Tq(t)},s=E_(i,n);if(r){let c=Ev(r.rows());o.savings={today:c.today,total:c.all}}let l=e.live;if(l){let c=l.insights.state;if(l.insights.refreshCoach(Date.now()),(l.insights.modelKnown||process.env.TOKEN_LENS_MODEL)&&(o.model=c.model),o.plan=l.insights.plan?.label??"",o.live=zq(l.status,l.helperOffer),s){let u=c.draft;o.draft=u&&{tokens:u.tokens,optimizedTokens:u.optimizedTokens,saved:u.saved,pct:u.pct,best:u.best},o.coach=c.coach?.insights.slice(0,3)??[]}}return{content:[{type:"text",text:"Token Lens box status."}],structuredContent:o}}),t.registerTool("optimize_prompt",{title:"Optimize a prompt for fewer tokens",description:"Shorten a prompt for a model with the same meaning (instructions, numbers, negations, code and quotes kept; task-aware). Returns the prompt to paste, savings and tips. Use when asked to shorten or optimize a prompt.",inputSchema:{prompt:H.string().describe("The prompt, as written."),model:Yn,level:H.enum(["auto",...fA]).optional().describe("Default auto."),conversation_tokens:H.number().int().nonnegative().optional().describe("Tokens already in the chat, if known."),pricing:uo},annotations:{readOnlyHint:!0,openWorldHint:!1},_meta:eg("Shortening the prompt\u2026","Shorter prompt ready")},async({prompt:n,model:i,level:o,conversation_tokens:s,pricing:l},c)=>{let u=Xs({prompt:n,model:Sn(i,l),conversationTokens:s,level:o??"auto"}),d=po(t,e.chat)?vq(u):bq(u),m=E_(c);!m&&i&&e.live?.insights.useModel(i);let p=m?{tokens:0,usd:0}:yq(u,d),g=u.optimized.saved-p.tokens;return a("optimize_prompt",u.profile,g,u.cost.saved.usd===null||p.usd===null?null:u.cost.saved.usd-p.usd),{content:[{type:"text",text:d}],structuredContent:{askTokens:p.tokens,askUsd:p.usd,model:u.profile.label,profile:u.profile.id,task:u.task.task,promptTokens:u.promptTokens,exact:u.exact,level:u.level,bestPrompt:u.best.text,kept:u.best.kept,optimizedTokens:u.optimized.tokens,saved:u.optimized.saved,savedUsd:u.cost.saved.usd,savedUsdPer1k:u.cost.saved.usdPer1k,promptUsd:u.cost.promptUsd,sendUsd:u.cost.sendUsd,pricing:u.cost.pricing??null,tips:u.tips.map(h=>h.text)},...po(t,e.chat)?{_meta:w_}:{}}}),t.registerTool("count_tokens",{title:"Count tokens",description:"Count a text's tokens for a model (exact for OpenAI; Claude and Gemini exact with the user's own API key set, else estimated). Use when asked how many tokens something is.",inputSchema:{text:H.string(),model:Yn,pricing:uo},annotations:{readOnlyHint:!0,openWorldHint:!1}},async({text:n,model:i,pricing:o})=>{let s=Sn(i,o),l=await kq(n,i,s.family)??Ce(n,s),{tokens:c,exact:u}=l,d=nt(c,s),m=Math.round(c/s.contextWindow*1e3)/10;return{content:[{type:"text",text:`${u?"":"\u2248"}${Q(c)} tokens for ${s.label} (${u?"exact":"estimate"}), ${m}% of a ~${Q(s.contextWindow)} token context. Costs ${Te(d)} as input each time it is sent (${Te(d===null?null:d*1e3)} per 1,000 sends). ${ba(s)}`}],structuredContent:{tokens:c,exact:u,profile:s.id,contextWindow:s.contextWindow,inputUsd:d,pricing:s.pricing??null}}}),t.registerTool("check_conversation",{title:"Check conversation token budget",description:"How full a chat's context is and what each message costs. Pass a token count (or the text). A long chat gets an optional new-chat handoff; present it as optional, since it can change later answers. Use for chat length, limits or forgotten context.",inputSchema:{model:Yn,conversation:H.string().optional().describe("The conversation so far, as text."),conversation_tokens:H.number().int().nonnegative().optional().describe("Or: its size in tokens."),latest_request:H.string().optional().describe("The latest message, to tailor the handoff."),pricing:uo},annotations:{readOnlyHint:!0,openWorldHint:!1},_meta:eg("Checking the chat\u2026","Chat checked")},async({model:n,conversation:i,conversation_tokens:o,latest_request:s,pricing:l})=>{let c=Sn(n,l),u=o??Ce(i??"",c).tokens,d=QP(u,c,s??i?.slice(-2e3)??"");return{content:[{type:"text",text:d.text}],structuredContent:d.structuredContent,...po(t,e.chat)?{_meta:w_}:{}}}),t.registerTool("optimize_agent",{title:"Find token hotspots and optimize an agent or conversation",description:"Find where an agent's tokens go (system prompt, tools, examples, history, next prompt), apply verified same-output fixes and return the optimized spec ready to paste, with the checks as evidence. Fixes that could change output are only listed, opt-in. Flags caching. Use when the user builds an agent or shares a system prompt or tools.",inputSchema:{model:Yn,system:H.string().optional().describe("System prompt / instructions."),tools:H.array(H.object({name:H.string(),description:H.string().optional(),parameters:H.any().optional()})).optional().describe("Tool definitions (parameters = JSON schema)."),examples:H.array(H.string()).optional().describe("Few-shot examples."),messages:H.array(H.object({role:H.enum(["user","assistant","tool","system"]),content:H.string(),toolCalls:H.array(H.string()).optional().describe("Names of tools called in this message.")})).optional().describe("Conversation history, oldest first."),prompt:H.string().optional().describe("The next user message."),pricing:uo,calls_per_month:H.number().int().positive().optional().describe("Calls per month, if known.")},annotations:{readOnlyHint:!0,openWorldHint:!1}},async({model:n,pricing:i,calls_per_month:o,...s})=>{let l=DA(s,Sn(n,i)),c=l.report;l.verification.passed&&a("optimize_agent",c.profile,l.savings.tokens,l.savings.usd);let u=v=>o&&v!==null?v*o:null,d=c.findings.filter(v=>v.risk==="review"),m=c.findings.filter(v=>v.risk==="safe"&&!v.applied),p=[l.verification.passed&&l.savings.tokens>0?`${Ci(l.savings)}${u(l.savings.usd)!==null?`, ${Te(u(l.savings.usd))} per month at ${Q(o)} calls`:""}: ${Q(l.before)} \u2192 ${Q(l.after)} tokens per call, same model and same output (checks below).`:l.verification.passed?"No wording savings to apply: the agent's text is already tight.":"No changes applied: a check failed, so the original spec is returned unchanged.",`Cost per call now: ${Te(c.costPerCall)} of input${l.savings.tokens>0?`, ${Te(nt(l.after,c.profile))} after the fixes`:""}. ${ba(c.profile)}`];m.length&&m[0].usd!==null&&p.push(`Also safe, needs a config change on your side: ${Te(m[0].usd)} more per call (${m[0].id}).`),d.length&&d[0].usd!==null&&p.push(`Opt-in fixes that could change the output (not applied): the biggest saves ${Te(d[0].usd)} more per call (${d[0].id}).`),p.push("",`Model profile: ${c.profile.label}. Total ${Q(c.total)} tokens per call; ${Q(c.fixedOverhead)} of it is re-sent every call (system + tools + examples).`,"","Where the tokens go:",...c.breakdown.map(v=>`- ${v.label}: ${Q(v.tokens)} (${c.total?Math.round(v.tokens/c.total*100):0}%)`)),c.growthPerTurn&&p.push(`Growing ~${Q(c.growthPerTurn)} tokens per user turn${c.turnsLeft!==null?`, about ${c.turnsLeft} turns of room left`:""}.`),p.push("",l.verification.passed?"Evidence the output is unchanged (same model, same tools, same instructions):":"Checks (one failed, so nothing was applied):",...l.verification.checks.map(v=>`${v.passed?"\u2713":"\u2717"} ${v.name}: ${v.detail}`));let g=c.findings.filter(v=>v.risk==="safe"),h=c.findings.filter(v=>v.risk==="review"),f=v=>v.id==="structured-output"?"saves every retry after a reply that doesn't parse":v.id==="price-threshold"&&!v.saves?"keeps the call at the lower rate":`saves ~${Q(v.saves)} tokens${v.usd!==null?`, ${Te(v.usd)} per call, ${Te(v.usd*1e3)} per 1,000 calls`:""}`;if(g.length&&p.push("","Safe fixes:",...g.map(v=>`- ${v.applied?"[applied] ":"[do this] "}${v.issue} ${v.fix} (${f(v)})`)),h.length&&p.push("","Could change the output, so not applied (ask the user):",...h.map(v=>`- ${v.issue} ${v.fix} (${f(v)})`)),l.verification.passed&&l.savings.tokens>0){let v=(C,S)=>["",`--- ${C} (paste this) ---`,S,"--- end ---"];l.spec.system&&l.spec.system!==s.system&&p.push(...v("optimized system prompt",l.spec.system)),l.spec.tools&&JSON.stringify(l.spec.tools)!==JSON.stringify(s.tools)&&p.push(...v("optimized tool definitions (JSON)",JSON.stringify(l.spec.tools,null,2))),l.spec.prompt&&l.spec.prompt!==s.prompt&&p.push(...v("optimized next prompt",l.spec.prompt)),l.spec.messages&&JSON.stringify(l.spec.messages)!==JSON.stringify(s.messages)&&p.push("","Conversation: exact repeats were replaced with pointers; the full list is in structuredContent.optimized.messages.")}s.system&&c.findings.some(v=>v.id==="volatile-prefix")&&p.push("","--- system prompt with the changing lines last (paste this) ---",Gv(l.spec.system??s.system).text,"--- end ---");let _=c.findings.some(v=>v.id==="structured-output")?Vv(s,c.profile):null;_&&p.push("",`--- strict JSON mode: add to the request (${c.profile.family==="anthropic"?"Anthropic Messages API":c.profile.family==="google"?"Gemini generateContent":"OpenAI-compatible API"}; paste this) ---`,JSON.stringify(_.setup,null,2),"--- end ---");let b=c.findings.some(v=>v.id==="lazy-tools")?Hv(l.spec.tools??[]):null;return b&&p.push("","--- on-demand tool loading: send these 2 tools instead of the full list (paste this) ---",JSON.stringify(b.tools,null,2),"--- end ---","Your runtime answers get_tool_schema with the tool's full definition (structuredContent.lazyTools.schemas) and runs invoke_tool by calling that tool with tool_input."),{content:[{type:"text",text:p.join(`
|
|
3855
|
+
`)}function vq(e){let t=e.optimized.originalTokens?Math.round(e.optimized.saved/e.optimized.originalTokens*100):0;return e.optimized.saved>0?`Shown in the Token Lens card with Copy: ${Q(e.promptTokens)} -> ${Q(e.optimized.tokens)} tokens (${t}% fewer). Don't repeat the prompt; reply in one short line.`:"Shown in the Token Lens card: already tight. Reply in one short line."}function yq(e,t){let r=Ce(t,e.profile).tokens+30,a=e.promptTokens+20,n=e.profile.pricing;return{tokens:r+a,usd:n?(r*n.inputPerMTok+a*n.outputPerMTok)/1e6:null}}async function kq(e,t,r){try{let a=typeof process<"u"?process.env:{};if(r==="anthropic"&&a.ANTHROPIC_API_KEY&&/^claude-[\w.-]+$/.test(t)){let n=await fetch("https://api.anthropic.com/v1/messages/count_tokens",{method:"POST",headers:{"x-api-key":a.ANTHROPIC_API_KEY,"anthropic-version":"2023-06-01","content-type":"application/json"},body:JSON.stringify({model:t,messages:[{role:"user",content:e}]}),signal:AbortSignal.timeout(5e3)}),i=await n.json();return n.ok&&typeof i.input_tokens=="number"?{tokens:i.input_tokens,exact:!0}:null}if(r==="google"&&a.GEMINI_API_KEY&&/^gemini-[\w.-]+$/.test(t)){let n=await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${t}:countTokens`,{method:"POST",headers:{"x-goog-api-key":a.GEMINI_API_KEY,"content-type":"application/json"},body:JSON.stringify({contents:[{parts:[{text:e}]}]}),signal:AbortSignal.timeout(5e3)}),i=await n.json();return n.ok&&typeof i.totalTokens=="number"?{tokens:i.totalTokens,exact:!0}:null}}catch{}return null}var _q={shorten:"Shorten my prompt before sending",optimize_prompt:"Optimized prompts",optimize_agent:"Agent fixes (per call)",audit_agent_rules:"Rule-file trims (per turn)",compact_data:"Compacted pasted data",compare_prompts:"Checked hand-shortened prompts"},wq="io.modelcontextprotocol/ui";function po(e,t){let r=e.server,a=r.getClientCapabilities();return a?!!a.extensions?.[wq]||/openai|chatgpt/i.test(r.getClientVersion()?.name??""):!!t}function Sq(e,t){return/code|cursor|vscode|windsurf|zed|cline|continue|jetbrains/i.test(e)?!1:t||/^claude-ai$|openai|chatgpt|le ?chat|mistral|perplexity|grok|gemini/i.test(e)}var Eq=["savings_report","plan_coach","live_tips","token_lens_helper"],xq="token-lens/via",e1=process.env.VERCEL_DEPLOYMENT_ID,a1=e1?gq("sha256").update(`token-lens-box:${e1}`).digest("hex").slice(0,32):hq(),w_={"token-lens/box":a1},i1=H.string().optional().describe("Set by the box itself.");function E_(e,t){return e?._meta?.[xq]==="box"||t?.box===a1}var o1={ui:{visibility:["app"]},"openai/visibility":"private","openai/widgetAccessible":!0},T_="For the Token Lens box only; never call it yourself.";function Tq(e){let t=e.server.getClientVersion()?.name??"";return[[/claude-code/i,"Claude Code"],[/claude/i,"Claude"],[/openai|chatgpt/i,"ChatGPT"],[/le ?chat|mistral/i,"Le Chat"],[/perplexity/i,"Perplexity"],[/grok/i,"Grok"],[/gemini/i,"Gemini"],[/cursor/i,"Cursor"],[/visual studio code|vscode/i,"VS Code"]].find(([a])=>a.test(t))?.[1]??""}var Aq={watching:"Shorten while you type is on: tips show here and as notifications.",starting:"Shorten while you type is starting.","cant-see-box":"Can't read the message box yet: click in it, or quit and reopen Claude.","needs-helper":"Shorten while you type needs Token Lens Helper on this Mac.",helper:"Shorten while you type is on: as you type in the message box, a Shorten button shows just above it when a shorter prompt says the same thing.","claude-closed":"Shorten while you type waits for the Claude app.",off:"Shorten while you type is off.",unsupported:""},Cq={watching:"Tips come as notifications while you type in the message box. Set up Token Lens Helper to get a Shorten button right above the box instead, so one click puts the shorter prompt in.","needs-helper":"To get tips while you type in the message box, set up Token Lens Helper. Then a Shorten button shows right above the box."};function zq(e,t){let r=t&&!["helper","off","unsupported"].includes(e);return{status:e,text:r&&Cq[e]||Aq[e]||"",helperOffer:r}}var s1={token_lens_state:{keep:["box"],description:T_},token_lens:{keep:["model","chat_tokens"],description:"Open the Token Lens box, where the user shortens prompts and sees costs without sending them to you. Use when asked to use Token Lens or shorten a prompt. Pass your rough estimate of this chat's tokens."},optimize_prompt:{keep:["prompt","model"],appOnly:!0,description:"Shorten a prompt to fewer tokens with the same meaning (every instruction, number, negation and code kept). Returns the shorter prompt and the savings. Use when asked to shorten or optimize a prompt."},check_conversation:{keep:["model","conversation_tokens"],appOnly:!0,description:"How full this chat's context is and what each new message costs. Pass your estimate of the chat's tokens; never copy the chat in."}};function Iq(e){let t=e.registerTool.bind(e);e.registerTool=(r,a,n)=>{let i=s1[r];if(i)return t(r,{...a,description:i.description,inputSchema:l1(a.inputSchema??{},i.keep),_meta:x_(a._meta,i.appOnly)},n)}}var t1=H.string().describe('Your model, e.g. "gpt-5".');function l1(e,t){return Object.fromEntries(Object.entries(e).filter(([r])=>t.includes(r)).map(([r,a])=>[r,r==="model"?a.isOptional()?t1.optional():t1:a]))}function x_(e,t){if(!t)return e;let r=e?.ui??{};return{...e,ui:{...r,visibility:["app"]},"openai/visibility":"private","openai/widgetAccessible":!0}}var Pq=["savings_report"];function Rq(e){let t=new Map,r=e.registerTool.bind(e);e.registerTool=(a,n,i)=>{let o=r(a,n,i);return t.set(a,{h:o,shape:n.inputSchema??{}}),o},e.server.oninitialized=()=>{if(Sq(e.server.getClientVersion()?.name??"",po(e)))for(let[a,{h:n,shape:i}]of t){let o=s1[a];o?(n.description=o.description,n.inputSchema=H.object(l1(i,o.keep)),n._meta=x_(n._meta,o.appOnly)):Pq.includes(a)?n._meta=x_(n._meta,!0):Eq.includes(a)||(n.enabled=!1)}}}function A_(e={}){let t=new Qm({name:"token-lens",version:fq}),r=e.ledger,a=(n,i,o,s)=>r?.record(n,i.id,o,s);return e.chat?Iq(t):process.env.TOKEN_LENS_TOOLS!=="all"&&Rq(t),e.live&&Oq(t,e.live),Mq(t,e.live),XP(t),t.registerPrompt("shorten",{title:"Shorten my prompt before sending",description:"Paste the prompt you're writing. Token Lens shortens it locally, keeping every instruction, fact, number, code block and constraint, and puts the shorter version in your message box. The long version never reaches the model.",argsSchema:{prompt:H.string().describe("The prompt you want to send, pasted in full."),model:H.string().optional().describe(`The model you'll send it to, e.g. "claude-sonnet-4-5" or "gpt-5". Leave blank for any model.`)}},({prompt:n,model:i})=>{let o=Xs({prompt:n,model:Sn(i??"",void 0),level:"auto"});a("shorten",o.profile,o.optimized.saved,o.cost.saved.usd);let s=o.optimized.originalTokens?Math.round(o.optimized.saved/o.optimized.originalTokens*100):0;return{description:o.optimized.saved>0?`Token Lens: ${Q(o.promptTokens)} -> ${Q(o.optimized.tokens)} tokens (${s}% fewer${o.cost.saved.usd===null?"":`, ${Te(o.cost.saved.usd)} saved`}). Kept: ${o.best.kept}.`:"Token Lens: already tight, sent unchanged.",messages:[{role:"user",content:{type:"text",text:o.optimized.saved>0?o.best.text:n}}]}}),t.registerTool("token_lens",{title:"Open Token Lens",description:"Open the Token Lens box in the chat, where the user shortens prompts without sending them to you. Use when asked to open or use Token Lens with no prompt given.",inputSchema:{model:Yn.optional(),chat_tokens:H.number().int().nonnegative().optional().describe("Rough tokens in this chat so far.")},annotations:{readOnlyHint:!0,openWorldHint:!1},_meta:eg("Opening Token Lens\u2026","Token Lens is open")},async({model:n,chat_tokens:i})=>{let o=Sn(n??"",void 0);n&&e.live?.insights.useModel(n);let s=i?QP(i,o,"").structuredContent:{};return{content:[{type:"text",text:po(t,e.chat)?'Token Lens box shown. Reply only "Open." If the user sees no box, ask for their prompt and call optimize_prompt.':"This app shows no Token Lens box. Ask the user to paste the prompt to shorten, then call optimize_prompt."}],structuredContent:{...s,panel:!0,model:n?o.label:"",profile:o.id},...po(t,e.chat)?{_meta:w_}:{}}}),t.registerTool("token_lens_state",{title:"Token Lens box status",description:T_,inputSchema:{box:i1},annotations:{readOnlyHint:!0,openWorldHint:!1},_meta:o1},async(n,i)=>{let o={app:Tq(t)},s=E_(i,n);if(r){let c=Ev(r.rows());o.savings={today:c.today,total:c.all}}let l=e.live;if(l){let c=l.insights.state;if(l.insights.refreshCoach(Date.now()),(l.insights.modelKnown||process.env.TOKEN_LENS_MODEL)&&(o.model=c.model),o.plan=l.insights.plan?.label??"",o.live=zq(l.status,l.helperOffer),s){let u=c.draft;o.draft=u&&{tokens:u.tokens,optimizedTokens:u.optimizedTokens,saved:u.saved,pct:u.pct,best:u.best},o.coach=c.coach?.insights.slice(0,3)??[]}}return{content:[{type:"text",text:"Token Lens box status."}],structuredContent:o}}),t.registerTool("optimize_prompt",{title:"Optimize a prompt for fewer tokens",description:"Shorten a prompt for a model with the same meaning (instructions, numbers, negations, code and quotes kept; task-aware). Returns the prompt to paste, savings and tips. Use when asked to shorten or optimize a prompt.",inputSchema:{prompt:H.string().describe("The prompt, as written."),model:Yn,level:H.enum(["auto",...fA]).optional().describe("Default auto."),conversation_tokens:H.number().int().nonnegative().optional().describe("Tokens already in the chat, if known."),pricing:uo},annotations:{readOnlyHint:!0,openWorldHint:!1},_meta:eg("Shortening the prompt\u2026","Shorter prompt ready")},async({prompt:n,model:i,level:o,conversation_tokens:s,pricing:l},c)=>{let u=Xs({prompt:n,model:Sn(i,l),conversationTokens:s,level:o??"auto"}),d=po(t,e.chat)?vq(u):bq(u),m=E_(c);!m&&i&&e.live?.insights.useModel(i);let p=m?{tokens:0,usd:0}:yq(u,d),g=u.optimized.saved-p.tokens;return a("optimize_prompt",u.profile,g,u.cost.saved.usd===null||p.usd===null?null:u.cost.saved.usd-p.usd),{content:[{type:"text",text:d}],structuredContent:{askTokens:p.tokens,askUsd:p.usd,model:u.profile.label,profile:u.profile.id,task:u.task.task,promptTokens:u.promptTokens,exact:u.exact,level:u.level,bestPrompt:u.best.text,kept:u.best.kept,optimizedTokens:u.optimized.tokens,saved:u.optimized.saved,savedUsd:u.cost.saved.usd,savedUsdPer1k:u.cost.saved.usdPer1k,promptUsd:u.cost.promptUsd,sendUsd:u.cost.sendUsd,pricing:u.cost.pricing??null,tips:u.tips.map(h=>h.text)},...po(t,e.chat)?{_meta:w_}:{}}}),t.registerTool("count_tokens",{title:"Count tokens",description:"Count a text's tokens for a model (exact for OpenAI; Claude and Gemini exact with the user's own API key set, else estimated). Use when asked how many tokens something is.",inputSchema:{text:H.string(),model:Yn,pricing:uo},annotations:{readOnlyHint:!0,openWorldHint:!1}},async({text:n,model:i,pricing:o})=>{let s=Sn(i,o),l=await kq(n,i,s.family)??Ce(n,s),{tokens:c,exact:u}=l,d=nt(c,s),m=Math.round(c/s.contextWindow*1e3)/10;return{content:[{type:"text",text:`${u?"":"\u2248"}${Q(c)} tokens for ${s.label} (${u?"exact":"estimate"}), ${m}% of a ~${Q(s.contextWindow)} token context. Costs ${Te(d)} as input each time it is sent (${Te(d===null?null:d*1e3)} per 1,000 sends). ${ba(s)}`}],structuredContent:{tokens:c,exact:u,profile:s.id,contextWindow:s.contextWindow,inputUsd:d,pricing:s.pricing??null}}}),t.registerTool("check_conversation",{title:"Check conversation token budget",description:"How full a chat's context is and what each message costs. Pass a token count (or the text). A long chat gets an optional new-chat handoff; present it as optional, since it can change later answers. Use for chat length, limits or forgotten context.",inputSchema:{model:Yn,conversation:H.string().optional().describe("The conversation so far, as text."),conversation_tokens:H.number().int().nonnegative().optional().describe("Or: its size in tokens."),latest_request:H.string().optional().describe("The latest message, to tailor the handoff."),pricing:uo},annotations:{readOnlyHint:!0,openWorldHint:!1},_meta:eg("Checking the chat\u2026","Chat checked")},async({model:n,conversation:i,conversation_tokens:o,latest_request:s,pricing:l})=>{let c=Sn(n,l),u=o??Ce(i??"",c).tokens,d=QP(u,c,s??i?.slice(-2e3)??"");return{content:[{type:"text",text:d.text}],structuredContent:d.structuredContent,...po(t,e.chat)?{_meta:w_}:{}}}),t.registerTool("optimize_agent",{title:"Find token hotspots and optimize an agent or conversation",description:"Find where an agent's tokens go (system prompt, tools, examples, history, next prompt), apply verified same-output fixes and return the optimized spec ready to paste, with the checks as evidence. Fixes that could change output are only listed, opt-in. Flags caching. Use when the user builds an agent or shares a system prompt or tools.",inputSchema:{model:Yn,system:H.string().optional().describe("System prompt / instructions."),tools:H.array(H.object({name:H.string(),description:H.string().optional(),parameters:H.any().optional()})).optional().describe("Tool definitions (parameters = JSON schema)."),examples:H.array(H.string()).optional().describe("Few-shot examples."),messages:H.array(H.object({role:H.enum(["user","assistant","tool","system"]),content:H.string(),toolCalls:H.array(H.string()).optional().describe("Names of tools called in this message.")})).optional().describe("Conversation history, oldest first."),prompt:H.string().optional().describe("The next user message."),pricing:uo,calls_per_month:H.number().int().positive().optional().describe("Calls per month, if known.")},annotations:{readOnlyHint:!0,openWorldHint:!1}},async({model:n,pricing:i,calls_per_month:o,...s})=>{let l=DA(s,Sn(n,i)),c=l.report;l.verification.passed&&a("optimize_agent",c.profile,l.savings.tokens,l.savings.usd);let u=v=>o&&v!==null?v*o:null,d=c.findings.filter(v=>v.risk==="review"),m=c.findings.filter(v=>v.risk==="safe"&&!v.applied),p=[l.verification.passed&&l.savings.tokens>0?`${Ci(l.savings)}${u(l.savings.usd)!==null?`, ${Te(u(l.savings.usd))} per month at ${Q(o)} calls`:""}: ${Q(l.before)} \u2192 ${Q(l.after)} tokens per call, same model and same output (checks below).`:l.verification.passed?"No wording savings to apply: the agent's text is already tight.":"No changes applied: a check failed, so the original spec is returned unchanged.",`Cost per call now: ${Te(c.costPerCall)} of input${l.savings.tokens>0?`, ${Te(nt(l.after,c.profile))} after the fixes`:""}. ${ba(c.profile)}`];m.length&&m[0].usd!==null&&p.push(`Also safe, needs a config change on your side: ${Te(m[0].usd)} more per call (${m[0].id}).`),d.length&&d[0].usd!==null&&p.push(`Opt-in fixes that could change the output (not applied): the biggest saves ${Te(d[0].usd)} more per call (${d[0].id}).`),p.push("",`Model profile: ${c.profile.label}. Total ${Q(c.total)} tokens per call; ${Q(c.fixedOverhead)} of it is re-sent every call (system + tools + examples).`,"","Where the tokens go:",...c.breakdown.map(v=>`- ${v.label}: ${Q(v.tokens)} (${c.total?Math.round(v.tokens/c.total*100):0}%)`)),c.growthPerTurn&&p.push(`Growing ~${Q(c.growthPerTurn)} tokens per user turn${c.turnsLeft!==null?`, about ${c.turnsLeft} turns of room left`:""}.`),p.push("",l.verification.passed?"Evidence the output is unchanged (same model, same tools, same instructions):":"Checks (one failed, so nothing was applied):",...l.verification.checks.map(v=>`${v.passed?"\u2713":"\u2717"} ${v.name}: ${v.detail}`));let g=c.findings.filter(v=>v.risk==="safe"),h=c.findings.filter(v=>v.risk==="review"),f=v=>v.id==="structured-output"?"saves every retry after a reply that doesn't parse":v.id==="price-threshold"&&!v.saves?"keeps the call at the lower rate":`saves ~${Q(v.saves)} tokens${v.usd!==null?`, ${Te(v.usd)} per call, ${Te(v.usd*1e3)} per 1,000 calls`:""}`;if(g.length&&p.push("","Safe fixes:",...g.map(v=>`- ${v.applied?"[applied] ":"[do this] "}${v.issue} ${v.fix} (${f(v)})`)),h.length&&p.push("","Could change the output, so not applied (ask the user):",...h.map(v=>`- ${v.issue} ${v.fix} (${f(v)})`)),l.verification.passed&&l.savings.tokens>0){let v=(C,S)=>["",`--- ${C} (paste this) ---`,S,"--- end ---"];l.spec.system&&l.spec.system!==s.system&&p.push(...v("optimized system prompt",l.spec.system)),l.spec.tools&&JSON.stringify(l.spec.tools)!==JSON.stringify(s.tools)&&p.push(...v("optimized tool definitions (JSON)",JSON.stringify(l.spec.tools,null,2))),l.spec.prompt&&l.spec.prompt!==s.prompt&&p.push(...v("optimized next prompt",l.spec.prompt)),l.spec.messages&&JSON.stringify(l.spec.messages)!==JSON.stringify(s.messages)&&p.push("","Conversation: exact repeats were replaced with pointers; the full list is in structuredContent.optimized.messages.")}s.system&&c.findings.some(v=>v.id==="volatile-prefix")&&p.push("","--- system prompt with the changing lines last (paste this) ---",Gv(l.spec.system??s.system).text,"--- end ---");let _=c.findings.some(v=>v.id==="structured-output")?Vv(s,c.profile):null;_&&p.push("",`--- strict JSON mode: add to the request (${c.profile.family==="anthropic"?"Anthropic Messages API":c.profile.family==="google"?"Gemini generateContent":"OpenAI-compatible API"}; paste this) ---`,JSON.stringify(_.setup,null,2),"--- end ---");let b=c.findings.some(v=>v.id==="lazy-tools")?Hv(l.spec.tools??[]):null;return b&&p.push("","--- on-demand tool loading: send these 2 tools instead of the full list (paste this) ---",JSON.stringify(b.tools,null,2),"--- end ---","Your runtime answers get_tool_schema with the tool's full definition (structuredContent.lazyTools.schemas) and runs invoke_tool by calling that tool with tool_input."),{content:[{type:"text",text:p.join(`
|
|
3856
3856
|
`)}],structuredContent:{lazyTools:b,structuredOutput:_?.setup??null,before:l.before,after:l.after,savedTokens:l.savings.tokens,savedUsdPerCall:l.savings.usd,savedUsdPer1kCalls:l.savings.usdPer1k,savedUsdPerMonth:u(l.savings.usd),costPerCallUsd:c.costPerCall,pricing:c.profile.pricing??null,breakdown:c.breakdown,growthPerTurn:c.growthPerTurn,turnsLeft:c.turnsLeft,findings:c.findings,optimized:l.spec,verification:l.verification}}}),t.registerTool("audit_agent_rules",{title:"Trim coding-agent rule files",description:"Trim coding-agent rule files (CLAUDE.md, AGENTS.md, .cursorrules and the like) without changing any instruction: repeats removed, wording tightened with every number and negation kept. Returns files to paste and savings per turn. Use when the user shares agent rule files.",inputSchema:{model:Yn,files:H.array(H.object({path:H.string(),content:H.string()})).min(1).describe("Each rule file: its path and full text."),repo_paths:H.array(H.string()).optional().describe("Repo file paths, to flag rules naming missing files."),pricing:uo},annotations:{readOnlyHint:!0,openWorldHint:!1}},async({model:n,files:i,repo_paths:o,pricing:s})=>{let l=Sn(n,s),c=MA(i,l,{knownPaths:o}),u=c.before-c.after;a("audit_agent_rules",l,c.saved.tokens,c.saved.usd);let d=[u>0?`${Ci(c.saved)} per turn: ${Q(c.before)} \u2192 ${Q(c.after)} tokens of rules re-sent every turn, same instructions.`:`Nothing to trim: ${Q(c.before)} tokens of rules, no repeats, wording already tight.`,ba(l)],m=c.findings.filter(g=>g.kind==="duplicate");m.length&&d.push("","Exact repeats removed:",...m.map(g=>`- ${g.path}:${g.line} ${g.text} (${g.detail})`));let p=c.findings.filter(g=>g.kind==="stale-path");p.length&&d.push("","Check these (not removed): they mention files that aren't in the repo.",...p.map(g=>`- ${g.path}:${g.line} ${g.detail}`));for(let g of c.files)g.trimmed!==i.find(h=>h.path===g.path)?.content&&d.push("",`--- ${g.path}, trimmed (${Q(g.tokens)} \u2192 ${Q(g.trimmedTokens)} tokens; paste this) ---`,g.trimmed,"--- end ---");return{content:[{type:"text",text:d.join(`
|
|
3857
3857
|
`)}],structuredContent:{before:c.before,after:c.after,savedTokensPerTurn:c.saved.tokens,savedUsdPerTurn:c.saved.usd,savedUsdPer1kTurns:c.saved.usdPer1k,files:c.files,findings:c.findings,pricing:l.pricing??null}}}),t.registerTool("list_model_profiles",{title:"List model profiles",description:"List the model families Token Lens knows, with context windows and prices.",inputSchema:{},annotations:{readOnlyHint:!0,openWorldHint:!1}},async()=>({content:[{type:"text",text:Object.values(hn).map(n=>`${n.id}: ${n.label}, ~${Q(n.contextWindow)} context, ${n.tokenizer==="o200k"?"exact":"estimated"} counts${n.reasoning?", reasoning model":""}, ${Dv(n.pricing)}`).join(`
|
|
3858
3858
|
`)}]})),t.registerTool("compact_data",{title:"Shrink pasted data without changing it",description:"Shrink pasted JSON or logs with the data unchanged (whitespace only, proven by parsing). Use when the user pastes large data.",inputSchema:{text:H.string().describe("Text with the pasted data, as written."),model:Yn},annotations:{readOnlyHint:!0,openWorldHint:!1}},async({text:n,model:i})=>{let o=Sn(i,void 0),s=Up(n,o),l=$t(s.saved,o);return a("compact_data",o,s.saved,l.usd),{content:[{type:"text",text:(s.saved>0?[`${Ci(l,"send")} (${Math.round(s.saved/s.before*100)}%): ${Q(s.before)} \u2192 ${Q(s.after)} tokens, same data. ${ba(o)}`,"","What changed:",...s.changes.map(u=>`- ${u}`),...s.checks.length?["","Evidence the data is unchanged:",...s.checks.map(u=>`\u2713 ${u.name}: ${u.detail}`)]:[],"","--- compact text, same data (paste this) ---",s.text,"--- end ---"]:[`No saving: ${s.skipped??"already compact."} ${Q(s.before)} tokens.`]).join(`
|
package/token-lens.mcpb
CHANGED
|
Binary file
|