@cachly-dev/mcp-server 0.10.220 → 0.10.221
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/src/handlers/cache.js +28 -28
- package/dist/src/index.js +66 -66
- package/dist/src/tools.js +1 -1
- package/glama.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{randomUUID as j}from"node:crypto";import{McpError as V,ErrorCode as Y}from"@modelcontextprotocol/sdk/types.js";import{readdir as F,stat as z,readFile as G}from"node:fs/promises";import{join as Q,relative as Z,extname as ee}from"node:path";import{computeEmbedding as R,hasEmbedProvider as K,embedProviderHint as J}from"../embeddings.js";import{einbettTakt as te}from"../einbett-takt.js";import{detectNamespace as P}from"../namespace.js";import{simpleHash as se}from"../confidence.js";import{scanKeys as ae}from"../utils.js";import{cachlyUrl as X}from"../cachly-url.js";const _e=new Set(["cache_get","cache_set","cache_delete","cache_exists","cache_ttl","cache_keys","cache_stats","semantic_search","detect_namespace","cache_warmup","index_project","cache_mset","cache_mget","cache_lock_acquire","cache_lock_release","cache_stream_set","cache_stream_get","cache_org_stats","set_cost_per_call"]);async function $e(W,p,$,b){switch(W){case"cache_get":{const{instance_id:n,key:e,org_id:a}=p,t=await $(n);let s=await t.get(e),i="direct";if(s===null&&a){const r=`org:${a}:sem:${e}`;s=await t.get(r),s!==null&&(i=`org:${a}:sem`)}if(s===null)return`Key \`${e}\` \u2192 **not found** (null)`;let o=s;try{o=JSON.stringify(JSON.parse(s),null,2)}catch{}const c=i!=="direct"?` _(from shared org namespace \`${i}\`)_`:"";return`Key \`${e}\`:${c}
|
|
2
2
|
\`\`\`
|
|
3
|
-
${
|
|
4
|
-
\`\`\``}case"cache_set":{const{instance_id:n,key:e,value:a,ttl:t,org_id:s}=
|
|
3
|
+
${o}
|
|
4
|
+
\`\`\``}case"cache_set":{const{instance_id:n,key:e,value:a,ttl:t,org_id:s}=p,i=await $(n);return t&&t>0?(await i.set(e,a,"EX",t),s&&await i.set(`org:${s}:sem:${e}`,a,"EX",t),`\u2705 Set \`${e}\` (TTL: ${t}s)${s?` + shared to \`org:${s}:sem\``:""}`):(await i.set(e,a),s&&await i.set(`org:${s}:sem:${e}`,a),`\u2705 Set \`${e}\` (no expiry)${s?` + shared to \`org:${s}:sem\``:""}`)}case"cache_delete":{const{instance_id:n,keys:e}=p;return`\u2705 Deleted **${await(await $(n)).del(...e)}** of ${e.length} key(s): ${e.map(s=>`\`${s}\``).join(", ")}`}case"cache_exists":{const{instance_id:n,keys:e}=p;return`**${await(await $(n)).exists(...e)}** of ${e.length} key(s) exist in cache.`}case"cache_ttl":{const{instance_id:n,key:e}=p,t=await(await $(n)).ttl(e);if(t===-2)return`Key \`${e}\` \u2192 **does not exist**`;if(t===-1)return`Key \`${e}\` \u2192 **no expiry** (persists forever)`;const s=Math.floor(t/60),i=t%60;return`Key \`${e}\` \u2192 TTL: **${t}s** (${s}m ${i}s remaining)`}case"cache_keys":{const{instance_id:n,pattern:e="*",count:a=50}=p,t=Math.min(a,500),s=await $(n),i=[],o=s.scanStream({match:e,count:100});await new Promise((r,l)=>{o.on("data",f=>{i.push(...f),i.length>=t&&(o.destroy(),r())}),o.on("end",r),o.on("error",l)});const c=i.slice(0,t);return c.length===0?`No keys found matching pattern \`${e}\`.`:[`Found **${c.length}** key(s) matching \`${e}\`:`,...c.map(r=>` \u2022 \`${r}\``),c.length===t?`
|
|
5
5
|
_(showing first ${t} \u2014 narrow pattern to see more)_`:""].filter(Boolean).join(`
|
|
6
|
-
`)}case"cache_stats":{const{instance_id:n}=
|
|
7
|
-
]+)`))?.[1]?.trim()??"n/a",
|
|
8
|
-
`).filter(
|
|
9
|
-
${
|
|
10
|
-
`)}`:" \u{1F5C2}\uFE0F Keyspace: (empty)"];try{const
|
|
11
|
-
`)}case"semantic_search":{const{instance_id:n,query:e,threshold:a=.82,namespace:t="cachly:sem",top_k:s=5,use_hybrid:i=!1,auto_namespace:
|
|
6
|
+
`)}case"cache_stats":{const{instance_id:n}=p,e=await $(n),[a,t,s]=await Promise.all([e.info("memory"),e.info("stats"),e.info("keyspace")]),i=(g,T)=>g.match(new RegExp(`${T}:([^\r
|
|
7
|
+
]+)`))?.[1]?.trim()??"n/a",o=i(a,"used_memory_human"),c=i(a,"used_memory_peak_human"),r=i(t,"keyspace_hits"),l=i(t,"keyspace_misses"),f=i(t,"instantaneous_ops_per_sec"),d=(await e.info("clients")).match(/connected_clients:(\d+)/)?.[1]??"n/a",y=parseInt(r)||0,w=parseInt(l)||0,x=y+w,S=x>0?(y/x*100).toFixed(1):"n/a",k=s.split(`
|
|
8
|
+
`).filter(g=>g.startsWith("db")).map(g=>` ${g.trim()}`),h=[`\u{1F4CA} **Cache Stats for instance \`${n}\`:**`,"",` \u{1F4BE} Memory used: ${o} (peak: ${c})`,` \u26A1 Ops/sec: ${f}`,` \u{1F3AF} Hit rate: ${S}% (${r} hits / ${l} misses)`,` \u{1F517} Clients: ${d}`,"",k.length>0?` \u{1F5C2}\uFE0F Keyspace:
|
|
9
|
+
${k.join(`
|
|
10
|
+
`)}`:" \u{1F5C2}\uFE0F Keyspace: (empty)"];try{const g=await b(`/api/v1/instances/${n}`);if(g.vector_token){const T=process.env.CACHLY_VECTOR_URL??"https://api.cachly.dev",O=await fetch(`${T}/v1/sem/${g.vector_token}/stats`,{signal:AbortSignal.timeout(5e3)});if(O.ok){const m=await O.json(),L=u=>u!==void 0?u.toLocaleString("en-US"):"n/a",U=m.total_hits??0,v=m.total_entries??0;if(U===0&&v===0)h.push("","**Cache usage:**"," \u{1F4ED} No cache hits yet \u2014 you're just getting started.",""," \u{1F680} Next: route LLM calls through cachly to start accumulating hits.",` Docs: ${X("/docs/semantic","cache-stats")}`);else{const u=m.savings?.week_over_week_pct,_=u!==void 0?` ${u>=0?"\u{1F4C8}":"\u{1F4C9}"} 7d trend: ${u>=0?"+":""}${u.toFixed(1)}% vs prev week`:"";h.push("","**Cache usage:**",` \u{1F4E6} Semantic entries: ${L(m.total_entries)}`,` \u{1F3AF} Total cache hits: ${L(m.total_hits)}`,` \u23F1\uFE0F Hits last 24h: ${L(m.savings?.hits_last_24h)}`,` \u{1F4C5} Hits last 7d: ${L(m.savings?.hits_last_7d)}`,..._?[_]:[])}m.top_hits&&m.top_hits.length>0&&(h.push("","\u{1F51D} Top cached prompts:"),m.top_hits.slice(0,3).forEach((_,E)=>{const C=_.prompt.length>50?`${_.prompt.slice(0,50)}\u2026`:_.prompt;h.push(` ${E+1}. "${C}" (hit ${_.hit_count}\xD7)`)}))}}}catch{}return h.join(`
|
|
11
|
+
`)}case"semantic_search":{const{instance_id:n,query:e,threshold:a=.82,namespace:t="cachly:sem",top_k:s=5,use_hybrid:i=!1,auto_namespace:o=!1}=p,c=o?P(e):t;if(!K())return`\u274C semantic_search requires an embedding provider.
|
|
12
12
|
|
|
13
13
|
Current: ${J()}
|
|
14
14
|
|
|
@@ -16,39 +16,39 @@ Easiest: sign in (CACHLY_JWT). Embeddings then run on the cachly servers in the
|
|
|
16
16
|
Or set OLLAMA_BASE_URL to use a local model (no key needed).
|
|
17
17
|
Your own key with a third-party provider works only if you name the provider yourself: CACHLY_EMBED_PROVIDER=<openai|gemini|mistral|cohere> plus its API key. The text is then sent to that provider.`;const r=await b(`/api/v1/instances/${n}`);if(!r.vector_token)return`\u274C Instance "${r.name}" needs a vector token for semantic search.
|
|
18
18
|
|
|
19
|
-
Every tier includes one. Open ${X(`/instances/${n}`,"health")} and rotate the vector token.`;const
|
|
19
|
+
Every tier includes one. Open ${X(`/instances/${n}`,"health")} and rotate the vector token.`;const l=await R(e),f=process.env.CACHLY_VECTOR_URL??`https://api.cachly.dev/v1/sem/${r.vector_token}`,d={embedding:l,namespace:c,threshold:a,top_k:s};i&&(d.hybrid=!0,d.prompt=e);const y=await fetch(`${f}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d),signal:AbortSignal.timeout(8e3)});if(!y.ok)throw new V(Y.InternalError,`Vector search failed: ${y.statusText}`);const w=await y.json();if(!w.length||w.length===1&&!w[0].found)return`\u{1F50D} No semantically similar entries found for:
|
|
20
20
|
_"${e}"_
|
|
21
21
|
|
|
22
|
-
Try lowering the threshold (current: ${a}) or using different keywords.`;const
|
|
23
|
-
`)}case"detect_namespace":{const{prompt:n}=
|
|
24
|
-
`)}case"cache_warmup":{const{instance_id:n,entries:e,namespace:a="cachly:sem",ttl:t,auto_namespace:s=!1}=
|
|
22
|
+
Try lowering the threshold (current: ${a}) or using different keywords.`;const x=await $(n),S=[`\u{1F50D} **Semantic search results** for: _"${e}"_`,` Threshold: ${a} \xB7 Namespace: \`${c}\``,""];for(const k of w){if(!k.found||!k.id)continue;const h=await x.get(`${c}:val:${k.id}`);S.push(`**Match** (similarity: ${((k.similarity??0)*100).toFixed(1)}%)`,` Prompt: _"${k.prompt??"(unknown)"}"_`,h?` Value: \`${h.slice(0,200)}${h.length>200?"\u2026":""}\``:" Value: _(evicted from cache)_","")}return S.join(`
|
|
23
|
+
`)}case"detect_namespace":{const{prompt:n}=p,e=P(n),a=e.split(":").pop(),t={code:"\u{1F4BB} Code \u2014 contains programming constructs or syntax",translation:"\u{1F310} Translation \u2014 asks to translate between languages",summary:"\u{1F4DD} Summary \u2014 requests a summary or key points (TL;DR)",qa:"\u2753 Q&A \u2014 a direct question or query",creative:"\u{1F3A8} Creative \u2014 general, creative, or conversational prompt"};return[`**Detected namespace:** \`${e}\``,`**Type:** ${t[a]??a}`,"",`_Prompt: "${n.slice(0,120)}${n.length>120?"\u2026":""}"_`,"","\u{1F4A1} Use this namespace in `semantic_search` or `cache_warmup` for better hit rates."," Set `auto_namespace: true` to apply this detection automatically."].join(`
|
|
24
|
+
`)}case"cache_warmup":{const{instance_id:n,entries:e,namespace:a="cachly:sem",ttl:t,auto_namespace:s=!1}=p;if(!K())return`\u274C cache_warmup requires an embedding provider.
|
|
25
25
|
|
|
26
26
|
Current: ${J()}
|
|
27
27
|
|
|
28
28
|
Supported: openai (default) \xB7 mistral \xB7 cohere \xB7 ollama (local) \xB7 gemini
|
|
29
|
-
Set CACHLY_EMBED_PROVIDER and the matching API key env var.`;const i=await b(`/api/v1/instances/${n}`),
|
|
30
|
-
`)}case"index_project":{const{instance_id:n,dir:e,extensions:a,max_files:t=100,ttl:s=86400,summary_chars:i=1200,namespace:
|
|
31
|
-
Extensions checked: ${[...r].join(", ")}`;const
|
|
32
|
-
${H}`;if(s>0?await m.set(M,I,"EX",s):await m.set(M,I),s>0?await m.set(
|
|
33
|
-
${H}`;await
|
|
34
|
-
`)}case"cache_mset":{const{instance_id:n,items:e}=
|
|
29
|
+
Set CACHLY_EMBED_PROVIDER and the matching API key env var.`;const i=await b(`/api/v1/instances/${n}`),o=process.env.CACHLY_VECTOR_URL??(i.vector_token?`https://api.cachly.dev/v1/sem/${i.vector_token}`:null),c=await $(n);let r=0,l=0;const f=[];for(const d of e){const y=d.namespace??(s?P(d.prompt):a),w=await R(d.prompt);let x=!1;if(o){const h=await fetch(`${o}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({embedding:w,namespace:y,threshold:.98}),signal:AbortSignal.timeout(8e3)}).catch(()=>null);h?.ok&&(x=(await h.json())[0]?.found??!1)}if(x){l++,f.push(` \u23ED\uFE0F _"${d.prompt.slice(0,60)}${d.prompt.length>60?"\u2026":""}"_ \u2192 already cached`);continue}const S=j(),k=`${y}:val:${S}`;if(t&&t>0?await c.set(k,d.value,"EX",t):await c.set(k,d.value),o){const h={id:S,prompt:d.prompt,namespace:y,embedding:w};t&&t>0&&(h.expires_at=new Date(Date.now()+t*1e3).toISOString()),await fetch(`${o}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h),signal:AbortSignal.timeout(8e3)}).catch(()=>{})}else{const h=`${y}:emb:${S}`,g=JSON.stringify({embedding:w,prompt:d.prompt});t&&t>0?await c.set(h,g,"EX",t):await c.set(h,g)}r++,f.push(` \u2705 _"${d.prompt.slice(0,60)}${d.prompt.length>60?"\u2026":""}"_ \u2192 \`${y}\``)}return["\u{1F525} **Cache Warmup Complete**","",` \u2705 Warmed: **${r}** new entries`,` \u23ED\uFE0F Skipped: **${l}** (already cached at \u2265 0.98 similarity)`,` \u{1F4E6} Total: ${e.length}`,s?" \u{1F3F7}\uFE0F Namespacing: auto-detected per prompt":` \u{1F3F7}\uFE0F Namespace: \`${a}\``,o?" \u{1F50D} Mode: pgvector HNSW":" \u{1F50D} Mode: Valkey SCAN (set CACHLY_VECTOR_URL for pgvector search)","",...f].join(`
|
|
30
|
+
`)}case"index_project":{const{instance_id:n,dir:e,extensions:a,max_files:t=100,ttl:s=86400,summary_chars:i=1200,namespace:o="cachly:sem:code",hintergrund:c=!1}=p,r=new Set((a??["ts","js","tsx","jsx","go","py","java","rs","md","kt","swift"]).map(v=>v.startsWith(".")?v:`.${v}`)),l=[];async function f(v){if(l.length>=t)return;const u=await F(v,{withFileTypes:!0}).catch(()=>null);if(u)for(const _ of u){if(l.length>=t)break;const E=Q(v,_.name);if(_.isDirectory()){if([".git","node_modules","dist","build",".next","__pycache__","vendor"].includes(_.name))continue;await f(E)}else _.isFile()&&r.has(ee(_.name).toLowerCase())&&l.push(E)}}if(await f(e),l.length===0)return`\u274C No matching files found in \`${e}\`.
|
|
31
|
+
Extensions checked: ${[...r].join(", ")}`;const d=await b(`/api/v1/instances/${n}`),y=process.env.CACHLY_VECTOR_URL??(d.vector_token?`https://api.cachly.dev/v1/sem/${d.vector_token}`:null),w=y&&K();let x=0,S=0,k=0,h=0,g=0;const T=[],O=new Date(Date.now()+s*1e3).toISOString(),m=await $(n),L=te({hintergrund:c});for(const v of l){const u=Z(e,v);let _,E;try{const A=await z(v);if(A.size>2e5){S++;continue}E=A.size,_=await G(v,"utf-8")}catch{k++;continue}const C=`cachly:idx:hash:${u}`,N=`${E}:${_.length}:${se(_)}`;if(await m.get(C)===N){const A=`cachly:idx:${u}`;s>0&&await m.expire(A,s),s>0&&await m.expire(C,s),g++;continue}const H=_.slice(0,i).replace(/\s+/g," ").trim(),M=`cachly:idx:${u}`,I=`File: ${u}
|
|
32
|
+
${H}`;if(s>0?await m.set(M,I,"EX",s):await m.set(M,I),s>0?await m.set(C,N,"EX",s):await m.set(C,N),x++,T.push(` \u2705 ${u}`),w&&L.darf())try{const A=`File: ${u}
|
|
33
|
+
${H}`;await L.warte();const B=await R(A).catch(q=>{throw L.fehler(q),q}),D=j();await fetch(`${y}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:D,prompt:A,namespace:o,embedding:B,expires_at:O}),signal:AbortSignal.timeout(8e3)}),await m.set(`${o}:val:${D}`,u,"EX",s),h++}catch{}}return[`\u{1F4C2} **index_project Complete** \u2014 ${w?"\u{1F50D} Keyword + \u{1F3AF} Semantic":"\u{1F50D} Keyword only (no embedding provider)"}`,"",` \u{1F4C1} Dir: ${e}`,` \u2705 Indexed: **${x}** files (new/changed)`,` \u267B\uFE0F Unchanged: ${g} files (hash match \u2014 skipped)`,...w?[` \u{1F3AF} Semantic: **${h}** files (vector-searchable)`]:[],` \u23ED\uFE0F Skipped: ${S} (too large or filtered)`,` \u274C Errors: ${k}`,` \u23F1\uFE0F TTL: ${s}s (${Math.round(s/3600)}h)`,"","\u{1F4A1} **Next steps:**",' 1. Use `smart_recall("how does auth work")` to find relevant files.'," 2. Re-run index_project after major refactors.",...w?[]:[" 3. Sign in (CACHLY_JWT) or set OLLAMA_BASE_URL to also enable semantic search."],"",...T.length<=20?T:[...T.slice(0,20),` \u2026 and ${T.length-20} more`]].join(`
|
|
34
|
+
`)}case"cache_mset":{const{instance_id:n,items:e}=p;if(!Array.isArray(e)||e.length===0)return"\u26A0\uFE0F No items provided.";const t=(await $(n)).pipeline();for(const s of e){const i=typeof s.value=="string"?s.value:JSON.stringify(s.value);s.ttl&&s.ttl>0?t.set(s.key,i,"EX",s.ttl):t.set(s.key,i)}return await t.exec(),`\u2705 **cache_mset** \u2013 ${e.length} key(s) written in one pipeline round-trip.
|
|
35
35
|
`+e.map(s=>` \u2022 \`${s.key}\`${s.ttl?` (TTL ${s.ttl}s)`:""}`).join(`
|
|
36
|
-
`)}case"cache_mget":{const{instance_id:n,keys:e}=
|
|
36
|
+
`)}case"cache_mget":{const{instance_id:n,keys:e}=p;if(!Array.isArray(e)||e.length===0)return"\u26A0\uFE0F No keys provided.";const t=await(await $(n)).mget(...e),s=e.map((i,o)=>{const c=t[o];return c===null?` \u2022 \`${i}\`: _null (miss)_`:` \u2022 \`${i}\`: ${c}`});return`\u2705 **cache_mget** \u2013 ${e.length} key(s) fetched in one round-trip.
|
|
37
37
|
`+s.join(`
|
|
38
|
-
`)}case"cache_lock_acquire":{const{instance_id:n,key:e,ttl_ms:a=5e3,retries:t=3,retry_delay_ms:s=50}=
|
|
38
|
+
`)}case"cache_lock_acquire":{const{instance_id:n,key:e,ttl_ms:a=5e3,retries:t=3,retry_delay_ms:s=50}=p,i=await $(n),o=`cachly:lock:${e}`,c=j();for(let r=0;r<=t;r++){if(await i.set(o,c,"PX",a,"NX")==="OK")return`\u{1F512} **cache_lock_acquire** \u2013 Lock acquired!
|
|
39
39
|
|
|
40
40
|
Key: \`${e}\`
|
|
41
|
-
Token: \`${
|
|
41
|
+
Token: \`${c}\`
|
|
42
42
|
TTL: ${a} ms
|
|
43
43
|
|
|
44
|
-
\u{1F4A1} Use **cache_lock_release** with this token to release early.`;r<t&&await new Promise(
|
|
44
|
+
\u{1F4A1} Use **cache_lock_release** with this token to release early.`;r<t&&await new Promise(f=>setTimeout(f,s))}return`\u274C **cache_lock_acquire** \u2013 Could not acquire lock for \`${e}\` after ${t+1} attempts.`}case"cache_lock_release":{const{instance_id:n,key:e,token:a}=p;return await(await $(n)).eval('if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end',1,`cachly:lock:${e}`,a)===1?`\u{1F513} **cache_lock_release** \u2013 Lock \`${e}\` released.`:`\u26A0\uFE0F **cache_lock_release** \u2013 Lock \`${e}\` expired or token mismatch.`}case"cache_stream_set":{const{instance_id:n,key:e,chunks:a,ttl:t}=p;if(!Array.isArray(a)||a.length===0)return"\u26A0\uFE0F No chunks provided.";const s=await $(n),i=`cachly:stream:${e}`;await s.del(i);const o=s.pipeline();for(const c of a)o.rpush(i,c);return t&&t>0&&o.expire(i,t),await o.exec(),`\u2705 **cache_stream_set** \u2013 ${a.length} chunk(s) stored.
|
|
45
45
|
Key: \`${e}\`
|
|
46
46
|
${t?` TTL: ${t}s
|
|
47
|
-
`:""} Total size: ${a.reduce((
|
|
47
|
+
`:""} Total size: ${a.reduce((c,r)=>c+r.length,0)} chars`}case"cache_stream_get":{const{instance_id:n,key:e}=p,a=await $(n),t=`cachly:stream:${e}`,s=await a.llen(t);if(s===0)return`\u26A0\uFE0F **cache_stream_get** \u2013 Cache miss for key \`${e}\`.`;const o=(await a.lrange(t,0,-1)).join(""),c=o.slice(0,500);return`\u2705 **cache_stream_get** \u2013 ${s} chunk(s) retrieved for \`${e}\`.
|
|
48
48
|
|
|
49
49
|
**Preview** (first 500 chars):
|
|
50
50
|
\`\`\`
|
|
51
|
-
${
|
|
52
|
-
\`\`\``}case"cache_org_stats":{const{instance_id:n,org_id:e}=
|
|
53
|
-
`)}case"set_cost_per_call":{const{instance_id:n,cost_per_call_usd:e}=
|
|
54
|
-
`)}default:return null}}export{
|
|
51
|
+
${c}${c.length<o.length?"\u2026":""}
|
|
52
|
+
\`\`\``}case"cache_org_stats":{const{instance_id:n,org_id:e}=p,a=await $(n),t=`org:${e}:sem`,i=(await ae(a,`${t}:*`,{max:1e4,timeoutMs:5e3})).length,o=[`\u{1F3E2} Org Cache Stats for org \`${e}\`:`,` \u{1F4E6} Shared entries: ${i}`,` \u{1F511} Namespace: ${t}`," \u{1F465} Benefit: All instances sharing this org_id serve from the same cache","",`\u{1F4A1} To enable sharing: set org_id="${e}" in cache_set calls`];try{const c=await b(`/api/v1/orgs/${e}/savings`);o.push("","Org-wide cache usage:",` \u{1F5A5}\uFE0F Instances: ${c.instance_count}`,` \u{1F3AF} Total cache hits: ${c.total_hits.toLocaleString("en-US")}`,` \u23F1\uFE0F Hits last 24h: ${c.hits_last_24h.toLocaleString("en-US")}`);const r=[...c.per_instance??[]].sort((l,f)=>f.total_hits-l.total_hits).slice(0,5);if(r.length>0){o.push(""," Top instances by hits:");for(const l of r)o.push(` \u2022 ${l.name||l.instance_id}: ${l.total_hits.toLocaleString("en-US")} hits`)}}catch{}return o.join(`
|
|
53
|
+
`)}case"set_cost_per_call":{const{instance_id:n,cost_per_call_usd:e}=p;if(!e||e<=0||e>100)throw new V(Y.InvalidParams,"cost_per_call_usd must be a positive number \u2264 100");await b(`/api/v1/instances/${n}/cost-per-call`,{method:"PUT",body:JSON.stringify({cost_per_call_usd:e})});const a={"0.02":"claude-opus-4.8","0.015":"gpt-5.5","0.009":"claude-sonnet-4.6","0.002":"gpt-5.5-mini","0.001":"claude-haiku-4.5"},t=a[e.toString()]?` (${a[e.toString()]})`:"";return[`\u2705 **Cost per call updated** \u2014 $${e.toFixed(4)}${t}`,"",`ROI savings in \`cache_stats\` will now use $${e.toFixed(4)} per avoided LLM call.`,"Run `cache_stats` to see your updated savings estimate."].join(`
|
|
54
|
+
`)}default:return null}}export{_e as CACHE_TOOL_NAMES,$e as handleCacheTool};
|