@hydra-acp/budgeter 0.1.11 → 0.1.12

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/bridge.js CHANGED
@@ -1 +1 @@
1
- import{watch as h}from"node:fs";import{basename as p,dirname as m}from"node:path";import{TransformerClient as u}from"./acp/transformer.js";import{Enforcer as l}from"./enforce.js";import{EventRouter as f}from"./router.js";import{CostTracker as g}from"./tracker.js";import{logger as v}from"./util/log.js";const o=v("bridge"),y=["response:session/update","request:session/prompt","lifecycle:session.opened","lifecycle:session.closed"];class x{constructor(e){this.opts=e;this.client=new u({daemonWsUrl:e.daemonWsUrl,token:e.token,intercepts:y}),this.tracker=new g({softLimit:e.softLimit,hardLimit:e.hardLimit,currency:e.currency,statePath:e.statePath}),this.enforcer=new l(this.client,o),this.router=new f(e.rule,this.tracker,this.enforcer,o)}opts;client;tracker;enforcer;router;watcher;watchTimer;stopped=!1;start(){this.client.on("request",e=>this.onRequest(e)),this.client.on("notification",e=>this.onNotification(e)),this.client.on("error",e=>{o.warn(`client error: ${e.message}`)}),this.client.on("open",()=>{this.registerSlashCommands()}),this.client.start(),this.startWatcher()}async registerSlashCommands(){try{await this.client.request("hydra-acp/commands/register",{commands:[{verb:"reset",description:"Reset accumulated cost baseline to current totals"},{verb:"status",description:"Show current spend vs. soft/hard limits"}]}),o.info("registered /hydra hydra-acp-budgeter {reset,status}")}catch(e){o.warn(`register_commands failed: ${e.message}`)}}stop(){this.stopped||(this.stopped=!0,this.watcher&&(this.watcher.close(),this.watcher=void 0),this.watchTimer&&(clearTimeout(this.watchTimer),this.watchTimer=void 0),this.client.stop())}startWatcher(){if(!this.opts.statePath)return;const e=m(this.opts.statePath),t=p(this.opts.statePath);try{this.watcher=h(e,(s,n)=>{n&&n!==t||this.watchTimer||(this.watchTimer=setTimeout(()=>{this.watchTimer=void 0;try{this.tracker.adoptFromDisk()}catch(r){o.warn(`adoptFromDisk failed: ${r.message}`)}},50))}),o.debug(`watching ${this.opts.statePath}`)}catch(s){o.warn(`fs.watch failed for ${e}: ${s.message}`)}}onRequest(e){if(e.method==="hydra-acp/commands/invoke"){this.handleExtensionCommand(e);return}if(e.method!=="hydra-acp/transformer/message"){this.client.reply(e.id,{action:"continue"});return}const t=e.params??{},s=typeof t.sessionId=="string"?t.sessionId:"",n=t.phase,r=t.method;if(!s||!n||!r){this.client.reply(e.id,{action:"continue"});return}if(n==="response"&&r==="session/update"){this.client.reply(e.id,{action:"continue"});const i=t.envelope,c=i&&typeof i=="object"&&!Array.isArray(i)?i.update:void 0;c&&this.router.onResponseUpdate(s,c).catch(d=>o.warn(`response update error: ${d.message}`));return}if(n==="request"&&r==="session/prompt"){this.router.onPromptRequest(s,t.envelope).then(i=>{i?this.client.reply(e.id,{action:"stop",payload:i}):this.client.reply(e.id,{action:"continue"})}).catch(i=>{o.warn(`prompt request error: ${i.message}`),this.client.reply(e.id,{action:"continue"})});return}this.client.reply(e.id,{action:"continue"})}handleExtensionCommand(e){const t=e.params??{},s=typeof t.verb=="string"?t.verb:"",n=typeof t.sessionId=="string"?t.sessionId:"",r=w(this.tracker,this.opts.currency,{verb:s,sessionId:n});r.kind==="ok"?this.client.reply(e.id,{text:r.text}):this.client.replyError(e.id,-32601,r.message)}onNotification(e){if(e.method!=="hydra-acp/transformer/session_event")return;const t=e.params??{},s=typeof t.event=="string"?t.event:"",n=typeof t.sessionId=="string"?t.sessionId:"";if(!(!s||!n)){if(s==="session.opened"){this.router.onLifecycle("session.opened",n).catch(r=>o.warn(`session.opened error: ${r.message}`));return}if(s==="session.closed"){this.router.onLifecycle("session.closed",n).catch(r=>o.warn(`session.closed error: ${r.message}`));return}}}}function w(a,e,t){if(t.verb==="reset"){a.reset();const s=t.sessionId?a.snapshotFor(t.sessionId):{total:0,currency:e};return{kind:"ok",text:`hydra-acp-budgeter: spend reset (total now ${s.total.toFixed(2)} ${s.currency})`}}if(t.verb==="status"){const s=a.snapshotFor(t.sessionId);return{kind:"ok",text:`hydra-acp-budgeter: total ${s.total.toFixed(2)} ${s.currency} (this session ${s.perSession.toFixed(2)}, soft ${s.soft}, hard ${s.hard}, state ${s.state})`}}return{kind:"error",message:`unknown verb: ${t.verb}`}}export{x as BudgeterBridge,w as runBudgeterCommand};
1
+ import{TransformerClient as p}from"./acp/transformer.js";import{Enforcer as u}from"./enforce.js";import{EventRouter as l}from"./router.js";import{CostTracker as m}from"./tracker.js";import{logger as h}from"./util/log.js";const o=h("bridge"),f=["response:session/update","request:session/prompt","lifecycle:session.opened","lifecycle:session.closed"];class I{constructor(e){this.opts=e;this.client=new p({daemonWsUrl:e.daemonWsUrl,token:e.token,intercepts:f});const s={get:t=>this.readSessionState(t),set:(t,r)=>this.writeSessionState(t,r),listSessionIds:()=>this.listSessionIds()};this.tracker=new m({softLimit:e.softLimit,hardLimit:e.hardLimit,currency:e.currency,store:s}),this.enforcer=new u(this.client,o),this.router=new l(e.rule,this.tracker,this.enforcer,o)}opts;client;tracker;enforcer;router;stopped=!1;start(){this.client.on("request",e=>this.onRequest(e)),this.client.on("notification",e=>this.onNotification(e)),this.client.on("error",e=>{o.warn(`client error: ${e.message}`)}),this.client.on("open",()=>{this.registerSlashCommands(),this.tracker.hydrateAll()}),this.client.start()}async registerSlashCommands(){try{await this.client.request("hydra-acp/commands/register",{commands:[{verb:"reset",description:"Reset accumulated cost baseline to current totals"},{verb:"status",description:"Show current spend vs. soft/hard limits"}]}),o.info("registered /hydra hydra-acp-budgeter {reset,status}")}catch(e){o.warn(`register_commands failed: ${e.message}`)}}stop(){this.stopped||(this.stopped=!0,this.client.stop())}async readSessionState(e){const s=await this.client.request("hydra-acp/session/extension_state/get",{sessionId:e,key:"state"});return g(s?.value)}async writeSessionState(e,s){await this.client.request("hydra-acp/session/extension_state/set",{sessionId:e,key:"state",value:s})}async listSessionIds(){const e=this.opts.daemonHttpBase.replace(/\/+$/,"");try{const s=await fetch(`${e}/v1/sessions?includeNonInteractive=1`,{headers:{Authorization:`Bearer ${this.opts.token}`}});if(!s.ok)return o.warn(`list sessions: HTTP ${s.status} \u2014 hydration will be lazy`),[];const t=await s.json(),r=[];for(const n of t.sessions??[])n&&typeof n.sessionId=="string"&&r.push(n.sessionId);return r}catch(s){return o.warn(`list sessions failed: ${s.message} \u2014 hydration will be lazy`),[]}}onRequest(e){if(e.method==="hydra-acp/commands/invoke"){this.handleExtensionCommand(e);return}if(e.method!=="hydra-acp/transformer/message"){this.client.reply(e.id,{action:"continue"});return}const s=e.params??{},t=typeof s.sessionId=="string"?s.sessionId:"",r=s.phase,n=s.method;if(!t||!r||!n){this.client.reply(e.id,{action:"continue"});return}if(r==="response"&&n==="session/update"){this.client.reply(e.id,{action:"continue"});const a=s.envelope,c=a&&typeof a=="object"&&!Array.isArray(a)?a.update:void 0;c&&this.router.onResponseUpdate(t,c).catch(d=>o.warn(`response update error: ${d.message}`));return}if(r==="request"&&n==="session/prompt"){this.router.onPromptRequest(t,s.envelope).then(a=>{a?this.client.reply(e.id,{action:"stop",payload:a}):this.client.reply(e.id,{action:"continue"})}).catch(a=>{o.warn(`prompt request error: ${a.message}`),this.client.reply(e.id,{action:"continue"})});return}this.client.reply(e.id,{action:"continue"})}handleExtensionCommand(e){const s=e.params??{},t=typeof s.verb=="string"?s.verb:"",r=typeof s.sessionId=="string"?s.sessionId:"",n=y(this.tracker,this.opts.currency,{verb:t,sessionId:r});n.kind==="ok"?this.client.reply(e.id,{text:n.text}):this.client.replyError(e.id,-32601,n.message)}onNotification(e){if(e.method!=="hydra-acp/transformer/session_event")return;const s=e.params??{},t=typeof s.event=="string"?s.event:"",r=typeof s.sessionId=="string"?s.sessionId:"";if(!(!t||!r)){if(t==="session.opened"){this.tracker.ensureHydrated(r).then(()=>this.router.onLifecycle("session.opened",r)).catch(n=>o.warn(`session.opened error: ${n.message}`));return}if(t==="session.closed"){this.router.onLifecycle("session.closed",r).catch(n=>o.warn(`session.closed error: ${n.message}`));return}}}}function y(i,e,s){if(s.verb==="reset"){i.reset();const t=s.sessionId?i.snapshotFor(s.sessionId):{total:0,currency:e};return{kind:"ok",text:`hydra-acp-budgeter: spend reset (total now ${t.total.toFixed(2)} ${t.currency})`}}if(s.verb==="status"){const t=i.snapshotFor(s.sessionId);return{kind:"ok",text:`hydra-acp-budgeter: total ${t.total.toFixed(2)} ${t.currency} (this session ${t.perSession.toFixed(2)}, soft ${t.soft}, hard ${t.hard}, state ${t.state})`}}return{kind:"error",message:`unknown verb: ${s.verb}`}}function g(i){if(!i||typeof i!="object"||Array.isArray(i))return;const e=i;if(typeof e.cost=="number")return{cost:e.cost,baseline:typeof e.baseline=="number"?e.baseline:0,currency:typeof e.currency=="string"?e.currency:void 0}}export{I as BudgeterBridge,y as runBudgeterCommand};
@@ -1 +1 @@
1
- import{relative as L,resolve as C}from"node:path";import{homedir as W}from"node:os";import{realpathSync as j}from"node:fs";function K(m){const f=m.match(/^(\d+)\s*([dhmwy])$/i);if(f!==null){const v=f[1],l=f[2];if(v===void 0||l===void 0)throw new Error(`invalid since spec: ${m}`);const k=parseInt(v,10),g=l.toLowerCase(),i=new Date;return g==="d"?i.setDate(i.getDate()-k):g==="h"?i.setHours(i.getHours()-k):g==="w"?i.setDate(i.getDate()-k*7):g==="m"?i.setMonth(i.getMonth()-k):g==="y"&&i.setFullYear(i.getFullYear()-k),i}const t=new Date(m);if(Number.isNaN(t.getTime()))throw new Error(`invalid since spec: ${m}`);return t}function z(m){const f=m.locByFiletype;if(f===void 0)return 0;let t=0;for(const v of Object.keys(f)){const l=f[v];l!==void 0&&(t+=l.added-l.removed)}return t}const M=new Map;function B(m){if(M.has(m))return M.get(m);try{const f=j(m);return M.set(m,f),f}catch{return M.set(m,m),m}}function N(m,f){let t=m;if(f.since!==void 0){const k=f.since.getTime(),g=[];for(const i of t)new Date(i.updatedAt).getTime()<k||g.push(i);t=g}if(f.dir!==void 0){const k=B(C(f.dir)),g=[];for(const i of t)i.cwd===void 0||i.cwd===""||(i.cwd.startsWith(k+"/")||i.cwd===k)&&g.push(i);t=g}if(f.host!==void 0&&f.host!=="all"){const k=f.host,g=[];for(const i of t)k==="local"?(!i.importedFromMachine||i.upstreamSessionId)&&g.push(i):i.importedFromMachine===k&&!i.upstreamSessionId&&g.push(i);t=g}if(f.interactive!==void 0){const k=f.interactive,g=[];for(const i of t)i.interactive===k&&g.push(i);t=g}const v=f.min??0,l=[];for(const k of t){let g;f.minMetric==="tokens"?g=k.contextTokens:f.minMetric==="loc"?g=z(k):g=k.costAmount,g>v&&l.push(k)}return l}function H(m){return m.dir!==void 0?C(m.dir):C(W())}function $(m,f,t){if(m===void 0||m==="")return"<unknown>";const v=B(C(m));if(f===void 0){const T=B(C(W()));return v===T?"~":v.startsWith(T+"/")?"~/"+v.slice(T.length+1):v}const l=B(C(t));let k=L(l,v);if(k===""||k===".")return"<root>";if(k.startsWith(".."))return"<unknown>";const g=k.split("/");if(g.length===0)return"<root>";const i=Math.min(f,g.length),S=g.slice(0,i);return S.length===0?"<root>":S.join("/")}function J(m,f,t={},v){let l=t.since;if(l===void 0&&t.bucket!==void 0){const e=new Date;t.bucket==="hour"?(e.setHours(e.getHours()-24),l=e):t.bucket==="day"?(e.setDate(e.getDate()-30),l=e):t.bucket==="week"?(e.setMonth(e.getMonth()-6),l=e):t.bucket==="month"&&(e.setFullYear(e.getFullYear()-2),l=e)}if(!(l!==void 0||t.bucket!==void 0||t.tokens===!0||t.loc===!0||t.by!==void 0||t.dir!==void 0||t.interactive!==void 0)){let e=0;for(const r of m)e+=r.costAmount;let o="";for(const r of m){o=r.costCurrency;break}return{kind:"total",row:{label:"All sessions",costAmount:e,sessionCount:m.length},currency:o}}const g={since:l,dir:t.dir,interactive:t.interactive,min:t.min,minMetric:t.loc===!0?"loc":t.tokens===!0?"tokens":"cost"},i=N(m,g),S=new Map;if(f!==void 0)for(const e of f){const o=S.get(e.sessionId);o===void 0?S.set(e.sessionId,[e]):o.push(e)}let T="";if(f!==void 0&&f.length>0)for(const e of f){T=e.currency;break}else if(i.length>0)for(const e of i){T=e.costCurrency;break}if(t.by==="filetype"&&t.bucket===void 0){const e=new Map,o=new Set;for(const u of i){const n=u.locByFiletype;if(n===void 0)continue;let a=!1;for(const d of Object.keys(n)){const w=n[d];if(w===void 0)continue;let s=e.get(d);s===void 0&&(s={added:0,removed:0,sessions:new Set},e.set(d,s)),s.added+=w.added,s.removed+=w.removed,(w.added>0||w.removed>0)&&(s.sessions.add(u.sessionId),a=!0)}a&&o.add(u.sessionId)}const r=[];for(const[u,n]of e){const a={label:u,costAmount:0,sessionCount:n.sessions.size,linesAdded:n.added,linesRemoved:n.removed};r.push({label:u,items:[a]})}return{kind:"grouped",groups:r,currency:T,totalSessions:o.size}}const I=e=>t.by==="dir"?$(e.cwd,t.depth,H(t)):t.by==="session"?e.sessionId.startsWith("hydra_session_")?e.sessionId.slice(14):e.sessionId:t.by==="model"?e.model===""?"<unknown>":e.model:t.by==="agent"?e.agentId===""?"<unknown>":e.agentId:"<all>",R=e=>{const o=new Date(e);if(Number.isNaN(o.getTime()))return"<invalid>";if(t.bucket==="hour"){const r=o.toLocaleDateString(void 0,{month:"2-digit",day:"2-digit"}),u=String(o.getHours()).padStart(2,"0");return`${r} ${u}:00`}if(t.bucket==="day")return o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"});if(t.bucket==="week"){const r=new Date(o),u=r.getDay(),n=r.getDate()-u+(u===0?-6:1);return r.setDate(n),r.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})}return t.bucket==="month"?o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit"}):o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})};if(t.by===void 0&&t.bucket===void 0){let e=0,o=0,r=0,u=0,n=0,a=0;for(const w of i){e+=w.costAmount;const s=S.get(w.sessionId);if(s!==void 0)for(const c of s)o+=c.deltaCost,t.tokens===!0&&(c.inputTokens!==void 0&&(r+=c.inputTokens),c.outputTokens!==void 0&&(u+=c.outputTokens),c.cacheReadTokens!==void 0&&(n+=c.cacheReadTokens),c.cacheWriteTokens!==void 0&&(a+=c.cacheWriteTokens))}const d={label:"All sessions",costAmount:e,deltaCost:o,sessionCount:i.length};if(t.tokens===!0&&(d.inputTokens=r,d.outputTokens=u,d.cacheReadTokens=n,d.cacheWriteTokens=a),t.loc===!0){let w=0,s=0;for(const c of i){const p=c.locByFiletype;if(p!==void 0)for(const b of Object.keys(p)){const h=p[b];h!==void 0&&(w+=h.added,s+=h.removed)}}d.linesAdded=w,d.linesRemoved=s}return{kind:"total",row:d,currency:T}}if(t.by!==void 0&&t.bucket===void 0){const e=new Map;for(const r of i){const u=I(r);let n=e.get(u);if(n===void 0&&(n={label:u,rows:{label:u,costAmount:0,deltaCost:0,sessionCount:0}},e.set(u,n)),n.rows.costAmount+=r.costAmount,n.rows.sessionCount=(n.rows.sessionCount??0)+1,t.tokens===!0&&(n.rows.inputTokens===void 0&&(n.rows.inputTokens=0),n.rows.inputTokens+=r.contextTokens),t.loc===!0){const d=r.locByFiletype;if(d!==void 0){n.rows.linesAdded===void 0&&(n.rows.linesAdded=0),n.rows.linesRemoved===void 0&&(n.rows.linesRemoved=0);for(const w of Object.keys(d)){const s=d[w];s!==void 0&&(n.rows.linesAdded+=s.added,n.rows.linesRemoved+=s.removed)}}}const a=S.get(r.sessionId);if(a!==void 0)for(const d of a)n.rows.deltaCost+=d.deltaCost,t.tokens===!0&&(n.rows.inputTokens===void 0&&(n.rows.inputTokens=0),n.rows.outputTokens===void 0&&(n.rows.outputTokens=0),n.rows.cacheReadTokens===void 0&&(n.rows.cacheReadTokens=0),n.rows.cacheWriteTokens===void 0&&(n.rows.cacheWriteTokens=0),d.inputTokens!==void 0&&(n.rows.inputTokens+=d.inputTokens),d.outputTokens!==void 0&&(n.rows.outputTokens+=d.outputTokens),d.cacheReadTokens!==void 0&&(n.rows.cacheReadTokens+=d.cacheReadTokens),d.cacheWriteTokens!==void 0&&(n.rows.cacheWriteTokens+=d.cacheWriteTokens))}const o=[];for(const r of e.values())o.push({label:r.label,items:[r.rows]});return{kind:"grouped",groups:o,currency:T}}const E=new Map;for(const e of i)E.set(e.sessionId,e);const A=new Map;if(f!==void 0){for(const e of f){if(!E.has(e.sessionId))continue;const o=A.get(e.sessionId);o===void 0?A.set(e.sessionId,[e]):o.push(e)}for(const e of A.values())e.sort((o,r)=>o.ts.localeCompare(r.ts))}const O=(e,o)=>{e._sessions===void 0&&(e._sessions=new Set),e._sessions.add(o),e.sessionCount=e._sessions.size},_=e=>{delete e._sessions},F=(e,o,r)=>{const u=R(o.ts);let n=e.get(u);n===void 0&&(n={bucket:u,costAmount:0,deltaCost:0,sessionCount:0},e.set(u,n)),n.costAmount+=r,n.deltaCost+=r,O(n,o.sessionId),t.tokens===!0&&(n.inputTokens===void 0||(o.inputTokens??0)>n.inputTokens)&&(n.inputTokens=o.inputTokens??n.inputTokens??0)};if(t.loc===!0&&t.bucket!==void 0){const e=new Set;for(const s of i)e.add(s.sessionId);const o=new Map;for(const s of i)o.set(s.sessionId,s);const r=(s,c)=>{let p=s.get(c);return p===void 0&&(p={bucket:c,costAmount:0,deltaCost:0,sessionCount:0,linesAdded:0,linesRemoved:0},s.set(c,p)),(p.linesAdded??void 0)===void 0&&(p.linesAdded=0),(p.linesRemoved??void 0)===void 0&&(p.linesRemoved=0),p},u=(s,c)=>{s.linesAdded=(s.linesAdded??0)+c.linesAdded,s.linesRemoved=(s.linesRemoved??0)+c.linesRemoved,s._sessions===void 0&&(s._sessions=new Set),s._sessions.add(c.sessionId),s.sessionCount=s._sessions.size},n=s=>{delete s._sessions},a=v??[];if(t.by==="filetype"){const s=new Map,c=new Set;for(const b of a){if(!e.has(b.sessionId)||l!==void 0&&new Date(b.ts)<l)continue;let h=s.get(b.filetype);h===void 0&&(h={label:b.filetype,buckets:new Map},s.set(b.filetype,h));const y=r(h.buckets,R(b.ts));u(y,b),c.add(b.sessionId)}const p=[];for(const b of s.values()){const h=Array.from(b.buckets.values());for(const y of h)n(y);h.sort((y,x)=>y.bucket.localeCompare(x.bucket)),h.length>0&&p.push({label:b.label,items:h})}return{kind:"timeSeriesGrouped",groups:p,currency:T,totalSessions:c.size}}if(t.by!==void 0){const s=new Map;for(const p of a){if(!e.has(p.sessionId)||l!==void 0&&new Date(p.ts)<l)continue;const b=o.get(p.sessionId);if(b===void 0)continue;const h=I(b);let y=s.get(h);y===void 0&&(y={label:h,buckets:new Map},s.set(h,y));const x=r(y.buckets,R(p.ts));u(x,p)}const c=[];for(const p of s.values()){const b=Array.from(p.buckets.values());for(const h of b)n(h);b.sort((h,y)=>h.bucket.localeCompare(y.bucket)),b.length>0&&c.push({label:p.label,items:b})}return{kind:"timeSeriesGrouped",groups:c,currency:T}}const d=new Map;for(const s of a){if(!e.has(s.sessionId)||l!==void 0&&new Date(s.ts)<l)continue;const c=r(d,R(s.ts));u(c,s)}const w=Array.from(d.values());for(const s of w)n(s);return w.sort((s,c)=>s.bucket.localeCompare(c.bucket)),{kind:"timeSeries",timeSeries:w,currency:T}}if(t.by!==void 0&&t.bucket!==void 0){const e=new Map,o=u=>{const n=I(u);let a=e.get(n);return a===void 0&&(a={label:n,buckets:new Map},e.set(n,a)),a.buckets};for(const u of i){const n=A.get(u.sessionId);if(n===void 0||n.length===0)continue;const a=o(u),d=n[0];if(d===void 0)continue;let w=d.cumulativeCost;for(let s=1;s<n.length;s++){const c=n[s];if(c===void 0)continue;const p=Math.max(0,c.cumulativeCost-w);w=c.cumulativeCost,!(l!==void 0&&new Date(c.ts)<l)&&F(a,c,p)}}const r=[];for(const u of e.values()){const n=Array.from(u.buckets.values());for(const a of n)_(a);n.sort((a,d)=>a.bucket.localeCompare(d.bucket)),n.length>0&&r.push({label:u.label,items:n})}return{kind:"timeSeriesGrouped",groups:r,currency:T}}const G=new Map;for(const e of i){const o=A.get(e.sessionId);if(o===void 0||o.length===0)continue;const r=o[0];if(r===void 0)continue;let u=r.cumulativeCost;for(let n=1;n<o.length;n++){const a=o[n];if(a===void 0)continue;const d=Math.max(0,a.cumulativeCost-u);u=a.cumulativeCost,!(l!==void 0&&new Date(a.ts)<l)&&F(G,a,d)}}const D=Array.from(G.values());for(const e of D)_(e);return D.sort((e,o)=>e.bucket.localeCompare(o.bucket)),{kind:"timeSeries",timeSeries:D,currency:T}}export{J as aggregate,N as applyFilters,K as parseSince};
1
+ import{relative as j,resolve as A}from"node:path";import{homedir as O}from"node:os";import{realpathSync as N}from"node:fs";function Q(g){const c=g.match(/^(\d+)\s*([dhmwy])$/i);if(c!==null){const T=c[1],a=c[2];if(T===void 0||a===void 0)throw new Error(`invalid since spec: ${g}`);const k=parseInt(T,10),l=a.toLowerCase(),i=new Date;return l==="d"?i.setDate(i.getDate()-k):l==="h"?i.setHours(i.getHours()-k):l==="w"?i.setDate(i.getDate()-k*7):l==="m"?i.setMonth(i.getMonth()-k):l==="y"&&i.setFullYear(i.getFullYear()-k),i}const t=new Date(g);if(Number.isNaN(t.getTime()))throw new Error(`invalid since spec: ${g}`);return t}function H(g){const c=g.locByFiletype;if(c===void 0)return 0;let t=0;for(const T of Object.keys(c)){const a=c[T];a!==void 0&&(t+=a.added-a.removed)}return t}const B=new Map;function I(g){if(B.has(g))return B.get(g);try{const c=N(g);return B.set(g,c),c}catch{return B.set(g,g),g}}function $(g,c){let t=g;if(c.since!==void 0){const k=c.since.getTime(),l=[];for(const i of t)new Date(i.updatedAt).getTime()<k||l.push(i);t=l}if(c.dir!==void 0){const k=I(A(c.dir)),l=[];for(const i of t)i.cwd===void 0||i.cwd===""||(i.cwd.startsWith(k+"/")||i.cwd===k)&&l.push(i);t=l}if(c.host!==void 0&&c.host!=="all"){const k=c.host,l=[];for(const i of t)k==="local"?(!i.importedFromMachine||i.upstreamSessionId)&&l.push(i):i.importedFromMachine===k&&!i.upstreamSessionId&&l.push(i);t=l}if(c.interactive!==void 0){const k=c.interactive,l=[];for(const i of t)i.interactive===k&&l.push(i);t=l}const T=c.min??0,a=[];for(const k of t){let l;c.minMetric==="tokens"?l=k.contextTokens:c.minMetric==="loc"?l=H(k):l=k.costAmount,l>T&&a.push(k)}return a}function q(g){return g.dir!==void 0?A(g.dir):A(O())}function P(g,c,t){if(g===void 0||g==="")return"<unknown>";const T=I(A(g));if(c===void 0){const y=I(A(O()));return T===y?"~":T.startsWith(y+"/")?"~/"+T.slice(y.length+1):T}const a=I(A(t));let k=j(a,T);if(k===""||k===".")return"<root>";if(k.startsWith(".."))return"<unknown>";const l=k.split("/");if(l.length===0)return"<root>";const i=Math.min(c,l.length),C=l.slice(0,i);return C.length===0?"<root>":C.join("/")}function U(g,c,t={},T){let a=t.since;if(a===void 0&&t.bucket!==void 0){const e=new Date;t.bucket==="hour"?(e.setHours(e.getHours()-24),a=e):t.bucket==="day"?(e.setDate(e.getDate()-30),a=e):t.bucket==="week"?(e.setMonth(e.getMonth()-6),a=e):t.bucket==="month"&&(e.setFullYear(e.getFullYear()-2),a=e)}if(!(a!==void 0||t.bucket!==void 0||t.tokens===!0||t.loc===!0||t.by!==void 0||t.dir!==void 0||t.interactive!==void 0)){let e=0;for(const r of g)e+=r.costAmount;let o="";for(const r of g){o=r.costCurrency;break}return{kind:"total",row:{label:"All sessions",costAmount:e,sessionCount:g.length},currency:o}}const l={since:a,dir:t.dir,interactive:t.interactive,min:t.min,minMetric:t.loc===!0?"loc":t.tokens===!0?"tokens":"cost"},i=$(g,l),C=new Map;if(c!==void 0)for(const e of c){const o=C.get(e.sessionId);o===void 0?C.set(e.sessionId,[e]):o.push(e)}let y="";if(c!==void 0&&c.length>0)for(const e of c){y=e.currency;break}else if(i.length>0)for(const e of i){y=e.costCurrency;break}if(t.by==="filetype"&&t.bucket===void 0){const e=new Map,o=new Set;for(const u of i){const n=u.locByFiletype;if(n===void 0)continue;let f=!1;for(const d of Object.keys(n)){const w=n[d];if(w===void 0)continue;let h=e.get(d);h===void 0&&(h={added:0,removed:0,sessions:new Set},e.set(d,h)),h.added+=w.added,h.removed+=w.removed,(w.added>0||w.removed>0)&&(h.sessions.add(u.sessionId),f=!0)}f&&o.add(u.sessionId)}const r=[];for(const[u,n]of e){const f={label:u,costAmount:0,sessionCount:n.sessions.size,linesAdded:n.added,linesRemoved:n.removed};r.push({label:u,items:[f],sessionCount:n.sessions.size})}return{kind:"grouped",groups:r,currency:y,totalSessions:o.size}}const D=e=>t.by==="dir"?P(e.cwd,t.depth,q(t)):t.by==="session"?e.sessionId.startsWith("hydra_session_")?e.sessionId.slice(14):e.sessionId:t.by==="model"?e.model===""?"<unknown>":e.model:t.by==="agent"?e.agentId===""?"<unknown>":e.agentId:"<all>",M=e=>{const o=new Date(e);if(Number.isNaN(o.getTime()))return"<invalid>";if(t.bucket==="hour"){const r=o.toLocaleDateString(void 0,{month:"2-digit",day:"2-digit"}),u=String(o.getHours()).padStart(2,"0");return`${r} ${u}:00`}if(t.bucket==="day")return o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"});if(t.bucket==="week"){const r=new Date(o),u=r.getDay(),n=r.getDate()-u+(u===0?-6:1);return r.setDate(n),r.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})}return t.bucket==="month"?o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit"}):o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})};if(t.by===void 0&&t.bucket===void 0){let e=0,o=0,r=0,u=0,n=0,f=0;for(const w of i){e+=w.costAmount;const h=C.get(w.sessionId);if(h!==void 0)for(const s of h)o+=s.deltaCost,t.tokens===!0&&(s.inputTokens!==void 0&&(r+=s.inputTokens),s.outputTokens!==void 0&&(u+=s.outputTokens),s.cacheReadTokens!==void 0&&(n+=s.cacheReadTokens),s.cacheWriteTokens!==void 0&&(f+=s.cacheWriteTokens))}const d={label:"All sessions",costAmount:e,deltaCost:o,sessionCount:i.length};if(t.tokens===!0&&(d.inputTokens=r,d.outputTokens=u,d.cacheReadTokens=n,d.cacheWriteTokens=f),t.loc===!0){let w=0,h=0;for(const s of i){const m=s.locByFiletype;if(m!==void 0)for(const p of Object.keys(m)){const b=m[p];b!==void 0&&(w+=b.added,h+=b.removed)}}d.linesAdded=w,d.linesRemoved=h}return{kind:"total",row:d,currency:y}}if(t.by!==void 0&&t.bucket===void 0){const e=new Map;for(const r of i){const u=D(r);let n=e.get(u);if(n===void 0&&(n={label:u,rows:{label:u,costAmount:0,deltaCost:0,sessionCount:0}},e.set(u,n)),n.rows.costAmount+=r.costAmount,n.rows.sessionCount=(n.rows.sessionCount??0)+1,t.tokens===!0&&(n.rows.inputTokens===void 0&&(n.rows.inputTokens=0),n.rows.inputTokens+=r.contextTokens),t.loc===!0){const d=r.locByFiletype;if(d!==void 0){n.rows.linesAdded===void 0&&(n.rows.linesAdded=0),n.rows.linesRemoved===void 0&&(n.rows.linesRemoved=0);for(const w of Object.keys(d)){const h=d[w];h!==void 0&&(n.rows.linesAdded+=h.added,n.rows.linesRemoved+=h.removed)}}}const f=C.get(r.sessionId);if(f!==void 0)for(const d of f)n.rows.deltaCost+=d.deltaCost,t.tokens===!0&&(n.rows.inputTokens===void 0&&(n.rows.inputTokens=0),n.rows.outputTokens===void 0&&(n.rows.outputTokens=0),n.rows.cacheReadTokens===void 0&&(n.rows.cacheReadTokens=0),n.rows.cacheWriteTokens===void 0&&(n.rows.cacheWriteTokens=0),d.inputTokens!==void 0&&(n.rows.inputTokens+=d.inputTokens),d.outputTokens!==void 0&&(n.rows.outputTokens+=d.outputTokens),d.cacheReadTokens!==void 0&&(n.rows.cacheReadTokens+=d.cacheReadTokens),d.cacheWriteTokens!==void 0&&(n.rows.cacheWriteTokens+=d.cacheWriteTokens))}const o=[];for(const r of e.values())o.push({label:r.label,items:[r.rows],sessionCount:r.rows.sessionCount});return{kind:"grouped",groups:o,currency:y}}const _=new Map;for(const e of i)_.set(e.sessionId,e);const R=new Map;if(c!==void 0){for(const e of c){if(!_.has(e.sessionId))continue;const o=R.get(e.sessionId);o===void 0?R.set(e.sessionId,[e]):o.push(e)}for(const e of R.values())e.sort((o,r)=>o.ts.localeCompare(r.ts))}const L=(e,o)=>{e._sessions===void 0&&(e._sessions=new Set),e._sessions.add(o),e.sessionCount=e._sessions.size},F=e=>{delete e._sessions},G=(e,o,r)=>{const u=M(o.ts);let n=e.get(u);n===void 0&&(n={bucket:u,costAmount:0,deltaCost:0,sessionCount:0},e.set(u,n)),n.costAmount+=r,n.deltaCost+=r,L(n,o.sessionId),t.tokens===!0&&(n.inputTokens===void 0||(o.inputTokens??0)>n.inputTokens)&&(n.inputTokens=o.inputTokens??n.inputTokens??0)};if(t.loc===!0&&t.bucket!==void 0){const e=new Set;for(const s of i)e.add(s.sessionId);const o=new Map;for(const s of i)o.set(s.sessionId,s);const r=(s,m)=>{let p=s.get(m);return p===void 0&&(p={bucket:m,costAmount:0,deltaCost:0,sessionCount:0,linesAdded:0,linesRemoved:0},s.set(m,p)),(p.linesAdded??void 0)===void 0&&(p.linesAdded=0),(p.linesRemoved??void 0)===void 0&&(p.linesRemoved=0),p},u=(s,m)=>{s.linesAdded=(s.linesAdded??0)+m.linesAdded,s.linesRemoved=(s.linesRemoved??0)+m.linesRemoved,s._sessions===void 0&&(s._sessions=new Set),s._sessions.add(m.sessionId),s.sessionCount=s._sessions.size},n=s=>{delete s._sessions},f=T??[];if(t.by==="filetype"){const s=new Map,m=new Set;for(const b of f){if(!e.has(b.sessionId)||a!==void 0&&new Date(b.ts)<a)continue;let v=s.get(b.filetype);v===void 0&&(v={label:b.filetype,buckets:new Map,sessions:new Set},s.set(b.filetype,v));const S=r(v.buckets,M(b.ts));u(S,b),v.sessions.add(b.sessionId),m.add(b.sessionId)}const p=[];for(const b of s.values()){const v=Array.from(b.buckets.values());for(const S of v)n(S);v.sort((S,E)=>S.bucket.localeCompare(E.bucket)),v.length>0&&p.push({label:b.label,items:v,sessionCount:b.sessions.size})}return{kind:"timeSeriesGrouped",groups:p,currency:y,totalSessions:m.size}}if(t.by!==void 0){const s=new Map;for(const p of f){if(!e.has(p.sessionId)||a!==void 0&&new Date(p.ts)<a)continue;const b=o.get(p.sessionId);if(b===void 0)continue;const v=D(b);let S=s.get(v);S===void 0&&(S={label:v,buckets:new Map,sessions:new Set},s.set(v,S));const E=r(S.buckets,M(p.ts));u(E,p),S.sessions.add(p.sessionId)}const m=[];for(const p of s.values()){const b=Array.from(p.buckets.values());for(const v of b)n(v);b.sort((v,S)=>v.bucket.localeCompare(S.bucket)),b.length>0&&m.push({label:p.label,items:b,sessionCount:p.sessions.size})}return{kind:"timeSeriesGrouped",groups:m,currency:y}}const d=new Map,w=new Set;for(const s of f){if(!e.has(s.sessionId)||a!==void 0&&new Date(s.ts)<a)continue;const m=r(d,M(s.ts));u(m,s),w.add(s.sessionId)}const h=Array.from(d.values());for(const s of h)n(s);return h.sort((s,m)=>s.bucket.localeCompare(m.bucket)),{kind:"timeSeries",timeSeries:h,currency:y,totalSessions:w.size}}if(t.by!==void 0&&t.bucket!==void 0){const e=new Map,o=u=>{const n=D(u);let f=e.get(n);return f===void 0&&(f={label:n,buckets:new Map,sessions:new Set},e.set(n,f)),f};for(const u of i){const n=R.get(u.sessionId);if(n===void 0||n.length===0)continue;const f=o(u),d=f.buckets;f.sessions.add(u.sessionId);const w=n[0];if(w===void 0)continue;let h=w.cumulativeCost;for(let s=1;s<n.length;s++){const m=n[s];if(m===void 0)continue;const p=Math.max(0,m.cumulativeCost-h);h=m.cumulativeCost,!(a!==void 0&&new Date(m.ts)<a)&&G(d,m,p)}}const r=[];for(const u of e.values()){const n=Array.from(u.buckets.values());for(const f of n)F(f);n.sort((f,d)=>f.bucket.localeCompare(d.bucket)),n.length>0&&r.push({label:u.label,items:n,sessionCount:u.sessions.size})}return{kind:"timeSeriesGrouped",groups:r,currency:y}}const W=new Map,z=new Set;for(const e of i){const o=R.get(e.sessionId);if(o===void 0||o.length===0)continue;const r=o[0];if(r===void 0)continue;let u=r.cumulativeCost,n=!1;for(let f=1;f<o.length;f++){const d=o[f];if(d===void 0)continue;const w=Math.max(0,d.cumulativeCost-u);u=d.cumulativeCost,!(a!==void 0&&new Date(d.ts)<a)&&(G(W,d,w),n=!0)}n&&z.add(e.sessionId)}const x=Array.from(W.values());for(const e of x)F(e);return x.sort((e,o)=>e.bucket.localeCompare(o.bucket)),{kind:"timeSeries",timeSeries:x,currency:y,totalSessions:z.size}}export{U as aggregate,$ as applyFilters,Q as parseSince};
@@ -1,2 +1,2 @@
1
- import{readFileSync as y}from"node:fs";import{homedir as l}from"node:os";import{resolve as m}from"node:path";import{logger as C}from"../util/log.js";const i=C("cost/daemon-client");function I(){if(process.env.HYDRA_ACP_TOKEN)return process.env.HYDRA_ACP_TOKEN;const e=m(process.env.HYDRA_ACP_HOME??m(l(),".hydra-acp"),"auth-token");try{const t=y(e,"utf8").trim();if(t.length>0)return t}catch(t){const n=t;n.code!=="ENOENT"&&i.debug(`auth-token read failed: ${n.message}`)}}function $(){if(process.env.HYDRA_ACP_DAEMON_URL)return process.env.HYDRA_ACP_DAEMON_URL;const e=m(process.env.HYDRA_ACP_HOME??m(l(),".hydra-acp"),"daemon.pid");try{const t=y(e,"utf8"),n=JSON.parse(t);if(typeof n.host=="string"&&typeof n.port=="number")return`http://${n.host}:${n.port}`}catch(t){const n=t;n.code!=="ENOENT"&&i.debug(`daemon.pid read failed: ${n.message}`)}return"http://127.0.0.1:8765"}function h(){const e=I();if(e!==void 0)return{daemonUrl:$(),token:e}}function U(e){const t=typeof e.sessionId=="string"?e.sessionId:void 0;if(t===void 0)return;const n=typeof e.updatedAt=="string"?e.updatedAt:"",s=(e._meta??{})["hydra-acp"]??{},d=typeof e.cwd=="string"?e.cwd:typeof s.cwd=="string"?s.cwd:void 0,o=typeof e.agentId=="string"?e.agentId:typeof s.agentId=="string"?s.agentId:"",c=typeof e.currentModel=="string"?e.currentModel:typeof s.currentModel=="string"?s.currentModel:"",p=typeof e.title=="string"?e.title:"",u=e.interactive===!0||s.interactive===!0,f=typeof e.importedFromMachine=="string"?e.importedFromMachine:typeof s.importedFromMachine=="string"?s.importedFromMachine:"",k=f.length>0?f:void 0,g=typeof e.upstreamSessionId=="string"?e.upstreamSessionId:typeof s.upstreamSessionId=="string"?s.upstreamSessionId:"",A=g.length>0?g:void 0,v=e.currentUsage,R=s.currentUsage,a=v??R,E=typeof a?.costAmount=="number"?a.costAmount:0,b=typeof a?.costCurrency=="string"?a.costCurrency:"",S=typeof a?.used=="number"?a.used:0;return{sessionId:t,cwd:d,agentId:o,model:c,interactive:u,costAmount:E,costCurrency:b,contextTokens:S,title:p,createdAt:"",updatedAt:n,importedFromMachine:k,upstreamSessionId:A}}async function P(){const e=h();if(e===void 0){i.debug("no daemon token resolved; skipping daemon fetch");return}const t=`${e.daemonUrl.replace(/\/$/,"")}/v1/sessions?includeNonInteractive=1`;let n;try{n=await fetch(t,{headers:{Authorization:`Bearer ${e.token}`}})}catch(o){i.debug(`daemon fetch failed (${t}): ${o.message}`);return}if(!n.ok){i.debug(`daemon returned HTTP ${n.status} for ${t}`);return}let r;try{r=await n.json()}catch(o){i.debug(`daemon response not JSON: ${o.message}`);return}const s=r;if(!Array.isArray(s.sessions)){i.debug("daemon response missing sessions[]");return}const d=[];for(const o of s.sessions)if(o&&typeof o=="object"&&!Array.isArray(o)){const c=U(o);c!==void 0&&d.push(c)}return d}function _(e){const t=typeof e.sessionId=="string"?e.sessionId:void 0,n=typeof e.ts=="string"?e.ts:void 0;if(t===void 0||n===void 0)return;const r=e.update??void 0,s=typeof r?.cost?.amount=="number"?r.cost.amount:0,d=typeof r?.cost?.currency=="string"?r.cost.currency:"USD",o=typeof r?.used=="number"?r.used:0;return{sessionId:t,ts:n,costCumulative:s,costCurrency:d,contextTokens:o}}async function x(e){const t=h();if(t===void 0)return;const n=new URLSearchParams({kinds:"usage_update"});e!==void 0&&n.set("since",e.toISOString());const r=`${t.daemonUrl.replace(/\/$/,"")}/v1/sessions/events?${n.toString()}`;let s;try{s=await fetch(r,{headers:{Authorization:`Bearer ${t.token}`}})}catch(c){i.debug(`events fetch failed: ${c.message}`);return}if(!s.ok){i.debug(`events endpoint HTTP ${s.status}`);return}const d=await s.text(),o=[];for(const c of d.split(`
2
- `)){const p=c.trim();if(p.length===0)continue;let u;try{u=JSON.parse(p)}catch{continue}if(u&&typeof u=="object"&&!Array.isArray(u)){const f=_(u);f!==void 0&&o.push(f)}}return o}export{x as fetchUsageEventsFromDaemon,P as listSessionsFromDaemon};
1
+ import{readFileSync as h}from"node:fs";import{homedir as k}from"node:os";import{resolve as m}from"node:path";import{logger as S}from"../util/log.js";const d=S("cost/daemon-client");function I(){if(process.env.HYDRA_ACP_TOKEN)return process.env.HYDRA_ACP_TOKEN;const e=m(process.env.HYDRA_ACP_HOME??m(k(),".hydra-acp"),"auth-token");try{const t=h(e,"utf8").trim();if(t.length>0)return t}catch(t){const n=t;n.code!=="ENOENT"&&d.debug(`auth-token read failed: ${n.message}`)}}function U(){if(process.env.HYDRA_ACP_DAEMON_URL)return process.env.HYDRA_ACP_DAEMON_URL;const e=m(process.env.HYDRA_ACP_HOME??m(k(),".hydra-acp"),"daemon.pid");try{const t=h(e,"utf8"),n=JSON.parse(t);if(typeof n.host=="string"&&typeof n.port=="number")return`http://${n.host}:${n.port}`}catch(t){const n=t;n.code!=="ENOENT"&&d.debug(`daemon.pid read failed: ${n.message}`)}return"http://127.0.0.1:8765"}function l(){const e=I();if(e!==void 0)return{daemonUrl:U(),token:e}}function T(e){const t=typeof e.sessionId=="string"?e.sessionId:void 0;if(t===void 0)return;const n=typeof e.updatedAt=="string"?e.updatedAt:"",s=(e._meta??{})["hydra-acp"]??{},c=typeof e.cwd=="string"?e.cwd:typeof s.cwd=="string"?s.cwd:void 0,o=typeof e.agentId=="string"?e.agentId:typeof s.agentId=="string"?s.agentId:"",i=typeof e.currentModel=="string"?e.currentModel:typeof s.currentModel=="string"?s.currentModel:"",a=typeof e.title=="string"?e.title:"",u=e.interactive===!0||s.interactive===!0,p=typeof e.importedFromMachine=="string"?e.importedFromMachine:typeof s.importedFromMachine=="string"?s.importedFromMachine:"",A=p.length>0?p:void 0,y=typeof e.upstreamSessionId=="string"?e.upstreamSessionId:typeof s.upstreamSessionId=="string"?s.upstreamSessionId:"",b=y.length>0?y:void 0,v=e.currentUsage,$=s.currentUsage,f=v??$,R=typeof f?.costAmount=="number"?f.costAmount:0,E=typeof f?.costCurrency=="string"?f.costCurrency:"",C=typeof f?.used=="number"?f.used:0;return{sessionId:t,cwd:c,agentId:o,model:i,interactive:u,costAmount:R,costCurrency:E,contextTokens:C,title:a,createdAt:"",updatedAt:n,importedFromMachine:A,upstreamSessionId:b}}async function w(){const e=l();if(e===void 0){d.debug("no daemon token resolved; skipping daemon fetch");return}const t=`${e.daemonUrl.replace(/\/$/,"")}/v1/sessions?includeNonInteractive=1`;let n;try{n=await fetch(t,{headers:{Authorization:`Bearer ${e.token}`}})}catch(o){d.debug(`daemon fetch failed (${t}): ${o.message}`);return}if(!n.ok){d.debug(`daemon returned HTTP ${n.status} for ${t}`);return}let r;try{r=await n.json()}catch(o){d.debug(`daemon response not JSON: ${o.message}`);return}const s=r;if(!Array.isArray(s.sessions)){d.debug("daemon response missing sessions[]");return}const c=[];for(const o of s.sessions)if(o&&typeof o=="object"&&!Array.isArray(o)){const i=T(o);i!==void 0&&c.push(i)}return c}function _(e){const t=typeof e.sessionId=="string"?e.sessionId:void 0,n=typeof e.ts=="string"?e.ts:void 0;if(t===void 0||n===void 0)return;const r=e.update??void 0,s=typeof r?.cost?.amount=="number"?r.cost.amount:0,c=typeof r?.cost?.currency=="string"?r.cost.currency:"USD",o=typeof r?.used=="number"?r.used:0;return{sessionId:t,ts:n,costCumulative:s,costCurrency:c,contextTokens:o}}async function D(e){const t=l();if(t===void 0)return;const n=new URLSearchParams({kinds:"usage_update"});e!==void 0&&n.set("since",e.toISOString());const r=`${t.daemonUrl.replace(/\/$/,"")}/v1/sessions/events?${n.toString()}`;let s;try{s=await fetch(r,{headers:{Authorization:`Bearer ${t.token}`}})}catch(i){d.debug(`events fetch failed: ${i.message}`);return}if(!s.ok){d.debug(`events endpoint HTTP ${s.status}`);return}const c=await s.text(),o=[];for(const i of c.split(`
2
+ `)){const a=i.trim();if(a.length===0)continue;let u;try{u=JSON.parse(a)}catch{continue}if(u&&typeof u=="object"&&!Array.isArray(u)){const p=_(u);p!==void 0&&o.push(p)}}return o}const g=new Map;async function O(e,t){const n=`${e}|${t}`,r=g.get(n);if(r!==void 0)return r;const s=l();if(s===void 0)return g.set(n,null),null;const c=`${s.daemonUrl.replace(/\/$/,"")}/v1/sessions/${encodeURIComponent(e)}/tools/${encodeURIComponent(t)}`;let o;try{o=await fetch(c,{headers:{Authorization:`Bearer ${s.token}`}})}catch(a){return d.debug(`tool blob fetch failed: ${a.message}`),g.set(n,null),null}if(!o.ok)return d.debug(`tool blob endpoint HTTP ${o.status} for ${t}`),g.set(n,null),null;const i=await o.text();return g.set(n,i),i}export{O as fetchToolBlob,D as fetchUsageEventsFromDaemon,w as listSessionsFromDaemon};
@@ -1,16 +1,17 @@
1
- function h(e){return"label"in e?e.label:e.bucket}function x(e){let n=0,i=!1;for(const o of e){if(o===void 0)continue;const f=String(o);f.length>n&&(n=f.length),o!==1&&(i=!0)}const r=i?8:7,l=e.map(o=>{if(o===void 0)return"";const f=String(o).padStart(n),a=(o===1?"session":"sessions").padEnd(r);return`${f} ${a}`}),u=n+1+r;return{strs:l,width:u}}function b(e){if(e<1e3)return String(e);let n,i;return e<1e6?(n=e/1e3,i="k"):e<1e9?(n=e/1e6,i="M"):(n=e/1e9,i="B"),(n%1===0?n.toFixed(0):n.toFixed(2)).replace(/\.?0+$/,"")+i}function w(e,n){let i=0;for(const u of e)u>i&&(i=u);if(i<=0||n<=0)return e.map(()=>"");const r=Math.max(1,n),l=[];for(const u of e){const o=u/i,f=Math.round(o*r),a=r-f;l.push("\u2588".repeat(f)+"\u2591".repeat(a))}return l}function E(e,n={}){const i=process.stdout.columns??80,r=n.histogram===!0,l=n.tokens===!0,u=n.loc===!0;let o="";const f=t=>`${t} session${t===1?"":"s"}`,a=(t,s)=>{if(u){let d=0;for(const c of s)d+=v(c);return`${$(d)} lines`}return A(t)};if(e.kind==="total"){const t=e.row.sessionCount,s=t!==void 0?f(t):e.row.label??"all sessions";o+=`Total: ${a(e.row.costAmount,[e.row])} across ${s}
2
- `}else if(e.kind==="grouped"){const t=[];let s=0;for(const c of e.groups)for(const p of c.items)t.push(p),s+=p.sessionCount??0;e.totalSessions!==void 0&&(s=e.totalSessions);const d=_(e.groups);o+=`Total: ${a(d,t)} across ${f(s)}
3
- `}else if(e.kind==="timeSeries"){const t=e.timeSeries.reduce((d,c)=>d+c.costAmount,0),s=e.timeSeries.reduce((d,c)=>d+(c.sessionCount??0),0);o+=`Total: ${a(t,e.timeSeries)} across ${f(s)}
4
- `}else if(e.kind==="timeSeriesGrouped"){const t=[];let s=0;for(const c of e.groups)for(const p of c.items)t.push(p),s+=p.sessionCount??0;e.totalSessions!==void 0&&(s=e.totalSessions);const d=_(e.groups);o+=`Total: ${a(d,t)} across ${f(s)}
5
- `}if(e.kind==="grouped")if(e.groups.every(s=>s.items.length===1)){const s=[];for(const d of e.groups){const c=d.items[0];c!==void 0&&s.push({...c,label:d.label})}s.sort((d,c)=>{const p=S(d,l,u);return S(c,l,u)-p}),r?o+=k(s,i,l,u):o+=j(s,l,u)}else for(const s of e.groups){o+=`
6
- ${s.label}:
7
- `;const d=s.items,c=d.some(p=>p.inputTokens!==void 0||p.outputTokens!==void 0);r?o+=k(d,i,l,u):o+=R(d,c,l,u)}else if(e.kind==="timeSeries"){const t=e.timeSeries.some(s=>s.inputTokens!==void 0||s.outputTokens!==void 0);r?o+=k(e.timeSeries,i,l,u):o+=R(e.timeSeries,t,l,u)}else if(e.kind==="timeSeriesGrouped")for(const t of e.groups){o+=`
8
- ${t.label}:
9
- `;const s=t.items.some(d=>d.inputTokens!==void 0||d.outputTokens!==void 0);r?o+=k(t.items,i,l,u):o+=R(t.items,s,l,u)}return o}function S(e,n,i){return i?v(e):n?T(e):e.costAmount}function B(e,n,i){return i?$(v(e)):n?b(T(e)):A(e.costAmount)}function j(e,n,i){if(e.length===0)return` (no data)
10
- `;let r=0;for(const t of e)t.label.length>r&&(r=t.label.length);const l=e.map(t=>B(t,n,i));let u=0;for(const t of l)t.length>u&&(u=t.length);const{strs:o,width:f}=x(e.map(t=>t.sessionCount)),a=[];for(let t=0;t<e.length;t++){const s=e[t];if(s===void 0)continue;const d=l[t]??"";let c=` ${s.label.padEnd(r)} ${d.padStart(u)}`;f>0&&(c+=` ${o[t]}`),a.push(c+`
11
- `)}return a.join("")}function k(e,n,i,r){if(e.length===0)return` (no data)
12
- `;let l=0;for(const m of e){const g=h(m);g.length>l&&(l=g.length)}const u=e.map(m=>{const g=S(m,i,r);return r?Math.abs(g):g}),o=e.map(m=>B(m,i,r));let f=0;for(const m of o)m.length>f&&(f=m.length);const{strs:a,width:t}=x(e.map(m=>m.sessionCount)),s=t>0?t+2:0;let d=n-l-f-s-8;d<1&&(d=e.length>1?1:0);const c=w(u,d),p=[];for(let m=0;m<e.length;m++){const g=e[m];if(g===void 0)continue;const G=h(g).padEnd(l),y=(o[m]??"").padStart(f);let C=` ${G} ${y} ${c[m]??""}`;t>0&&(C+=` ${a[m]}`),p.push(C+`
13
- `)}return p.join("")}function R(e,n,i,r){if(e.length===0)return` (no data)
14
- `;let l=28;for(const o of e){const f=h(o);f.length>l&&(l=f.length)}let u=" ";u+=N("",l),u+=" ",r?u+=" +Added -Removed Net":i?u+=" Tokens":(u+=" Cost",n&&(u+=" Tokens")),u+=`
15
- `;for(const o of e){const f=h(o).padEnd(l);let a=" ";if(a+=f,a+=" ",r){const t=o.linesAdded??0,s=o.linesRemoved??0;a+=t.toLocaleString().padStart(10),a+=" ",a+=s.toLocaleString().padStart(8),a+=" ",a+=$(t-s).padStart(8)}else if(i){const t=T(o);a+=b(t).padStart(12)}else{const t=o.costAmount??0;if(a+=A(t).padStart(10),n){const s=T(o);a+=" ".padStart(2),a+=b(s).padStart(12)}}u+=a+`
16
- `}return u}function F(e){const n={kind:e.kind,currency:e.currency};if(e.kind==="total")n.row=L(e.row);else if(e.kind==="grouped"){const i=e.groups.slice().sort((r,l)=>r.label.localeCompare(l.label));n.groups=i.map(r=>({label:r.label,items:r.items.map(L)}))}else if(e.kind==="timeSeries")n.timeSeries=e.timeSeries.map(W);else if(e.kind==="timeSeriesGrouped"){const i=e.groups.slice().sort((r,l)=>r.label.localeCompare(l.label));n.groups=i.map(r=>({label:r.label,items:r.items.map(W)}))}return JSON.stringify(n,null,2)}function L(e){const n={label:e.label,costAmount:e.costAmount};return e.deltaCost!==void 0&&(n.deltaCost=e.deltaCost),e.inputTokens!==void 0&&(n.inputTokens=e.inputTokens),e.outputTokens!==void 0&&(n.outputTokens=e.outputTokens),e.cacheReadTokens!==void 0&&(n.cacheReadTokens=e.cacheReadTokens),e.cacheWriteTokens!==void 0&&(n.cacheWriteTokens=e.cacheWriteTokens),e.linesAdded!==void 0&&(n.linesAdded=e.linesAdded),e.linesRemoved!==void 0&&(n.linesRemoved=e.linesRemoved),(e.linesAdded!==void 0||e.linesRemoved!==void 0)&&(n.linesNet=(e.linesAdded??0)-(e.linesRemoved??0)),n}function W(e){const n={bucket:e.bucket,costAmount:e.costAmount,deltaCost:e.deltaCost};return e.inputTokens!==void 0&&(n.inputTokens=e.inputTokens),e.outputTokens!==void 0&&(n.outputTokens=e.outputTokens),e.cacheReadTokens!==void 0&&(n.cacheReadTokens=e.cacheReadTokens),e.cacheWriteTokens!==void 0&&(n.cacheWriteTokens=e.cacheWriteTokens),e.linesAdded!==void 0&&(n.linesAdded=e.linesAdded),e.linesRemoved!==void 0&&(n.linesRemoved=e.linesRemoved),(e.linesAdded!==void 0||e.linesRemoved!==void 0)&&(n.linesNet=(e.linesAdded??0)-(e.linesRemoved??0)),n}function A(e){return`$${e.toFixed(2)}`}function v(e){const n=e.linesAdded??0,i=e.linesRemoved??0;return n-i}function $(e){return`${e>0?"+":""}${e.toLocaleString()}`}function T(e){let n=0;return e.inputTokens!==void 0&&(n+=e.inputTokens),e.outputTokens!==void 0&&(n+=e.outputTokens),n}function N(e,n){return e.length>=n?e:e+" ".repeat(n-e.length)}function _(e){let n=0;for(const i of e)for(const r of i.items)n+=r.costAmount;return n}export{F as renderJson,E as renderText};
1
+ function k(e){return"label"in e?e.label:e.bucket}function w(e){let n=0,s=!1;for(const o of e){if(o===void 0)continue;const f=String(o);f.length>n&&(n=f.length),o!==1&&(s=!0)}const l=s?8:7,r=e.map(o=>{if(o===void 0)return"";const f=String(o).padStart(n),d=(o===1?"session":"sessions").padEnd(l);return`${f} ${d}`}),u=n+1+l;return{strs:r,width:u}}function b(e){if(e<1e3)return String(e);let n,s;return e<1e6?(n=e/1e3,s="k"):e<1e9?(n=e/1e6,s="M"):(n=e/1e9,s="B"),(n%1===0?n.toFixed(0):n.toFixed(2)).replace(/\.?0+$/,"")+s}function j(e,n){let s=0;for(const u of e)u>s&&(s=u);if(s<=0||n<=0)return e.map(()=>"");const l=Math.max(1,n),r=[];for(const u of e){const o=u/s,f=Math.round(o*l),d=l-f;r.push("\u2588".repeat(f)+"\u2591".repeat(d))}return r}function E(e,n={}){const s=process.stdout.columns??80,l=n.histogram===!0,r=n.tokens===!0,u=n.loc===!0;let o="";const f=t=>`${t} session${t===1?"":"s"}`,d=(t,i)=>{if(u){let a=0;for(const c of i)a+=R(c);return`${A(a)} lines`}return S(t)};if(e.kind==="total"){const t=e.row.sessionCount,i=t!==void 0?f(t):e.row.label??"all sessions";o+=`Total: ${d(e.row.costAmount,[e.row])} across ${i}
2
+ `}else if(e.kind==="grouped"){const t=[];let i=0;for(const c of e.groups)for(const p of c.items)t.push(p),i+=p.sessionCount??0;e.totalSessions!==void 0&&(i=e.totalSessions);const a=_(e.groups);o+=`Total: ${d(a,t)} across ${f(i)}
3
+ `}else if(e.kind==="timeSeries"){const t=e.timeSeries.reduce((a,c)=>a+c.costAmount,0),i=e.totalSessions??e.timeSeries.reduce((a,c)=>a+(c.sessionCount??0),0);o+=`Total: ${d(t,e.timeSeries)} across ${f(i)}
4
+ `}else if(e.kind==="timeSeriesGrouped"){const t=[];let i=0;for(const c of e.groups)for(const p of c.items)t.push(p),i+=p.sessionCount??0;e.totalSessions!==void 0&&(i=e.totalSessions);const a=_(e.groups);o+=`Total: ${d(a,t)} across ${f(i)}
5
+ `}if(n.windowLabel!==void 0&&n.windowLabel.length>0&&(o+=`Window: ${n.windowLabel}
6
+ `),e.kind==="grouped")if(e.groups.every(i=>i.items.length===1)){const i=[];for(const a of e.groups){const c=a.items[0];c!==void 0&&i.push({...c,label:a.label})}i.sort((a,c)=>{const p=$(a,r,u);return $(c,r,u)-p}),l?o+=T(i,s,r,u):o+=N(i,r,u)}else for(const i of e.groups){o+=`
7
+ ${i.label}: ${x(i.items,i.sessionCount,r,u)}
8
+ `;const a=i.items,c=a.some(p=>p.inputTokens!==void 0||p.outputTokens!==void 0);l?o+=T(a,s,r,u):o+=v(a,c,r,u)}else if(e.kind==="timeSeries"){const t=e.timeSeries.some(i=>i.inputTokens!==void 0||i.outputTokens!==void 0);l?o+=T(e.timeSeries,s,r,u):o+=v(e.timeSeries,t,r,u)}else if(e.kind==="timeSeriesGrouped")for(const t of e.groups){o+=`
9
+ ${t.label}: ${x(t.items,t.sessionCount,r,u)}
10
+ `;const i=t.items.some(a=>a.inputTokens!==void 0||a.outputTokens!==void 0);l?o+=T(t.items,s,r,u):o+=v(t.items,i,r,u)}return o}function $(e,n,s){return s?R(e):n?h(e):e.costAmount}function L(e,n,s){return s?A(R(e)):n?b(h(e)):S(e.costAmount)}function x(e,n,s,l){let r=0;for(const d of e)r+=l?R(d):s?h(d):d.costAmount;const u=l?`${A(r)} lines`:s?`${b(r)} tokens`:S(r),o=n??e.reduce((d,t)=>d+(t.sessionCount??0),0);return o===0?u:`${u} across ${o} ${o===1?"session":"sessions"}`}function N(e,n,s){if(e.length===0)return` (no data)
11
+ `;let l=0;for(const t of e)t.label.length>l&&(l=t.label.length);const r=e.map(t=>L(t,n,s));let u=0;for(const t of r)t.length>u&&(u=t.length);const{strs:o,width:f}=w(e.map(t=>t.sessionCount)),d=[];for(let t=0;t<e.length;t++){const i=e[t];if(i===void 0)continue;const a=r[t]??"";let c=` ${i.label.padEnd(l)} ${a.padStart(u)}`;f>0&&(c+=` ${o[t]}`),d.push(c+`
12
+ `)}return d.join("")}function T(e,n,s,l){if(e.length===0)return` (no data)
13
+ `;let r=0;for(const m of e){const g=k(m);g.length>r&&(r=g.length)}const u=e.map(m=>{const g=$(m,s,l);return l?Math.abs(g):g}),o=e.map(m=>L(m,s,l));let f=0;for(const m of o)m.length>f&&(f=m.length);const{strs:d,width:t}=w(e.map(m=>m.sessionCount)),i=t>0?t+2:0;let a=n-r-f-i-8;a<1&&(a=e.length>1?1:0);const c=j(u,a),p=[];for(let m=0;m<e.length;m++){const g=e[m];if(g===void 0)continue;const G=k(g).padEnd(r),y=(o[m]??"").padStart(f);let C=` ${G} ${y} ${c[m]??""}`;t>0&&(C+=` ${d[m]}`),p.push(C+`
14
+ `)}return p.join("")}function v(e,n,s,l){if(e.length===0)return` (no data)
15
+ `;let r=28;for(const o of e){const f=k(o);f.length>r&&(r=f.length)}let u=" ";u+=z("",r),u+=" ",l?u+=" +Added -Removed Net":s?u+=" Tokens":(u+=" Cost",n&&(u+=" Tokens")),u+=`
16
+ `;for(const o of e){const f=k(o).padEnd(r);let d=" ";if(d+=f,d+=" ",l){const t=o.linesAdded??0,i=o.linesRemoved??0;d+=t.toLocaleString().padStart(10),d+=" ",d+=i.toLocaleString().padStart(8),d+=" ",d+=A(t-i).padStart(8)}else if(s){const t=h(o);d+=b(t).padStart(12)}else{const t=o.costAmount??0;if(d+=S(t).padStart(10),n){const i=h(o);d+=" ".padStart(2),d+=b(i).padStart(12)}}u+=d+`
17
+ `}return u}function F(e){const n={kind:e.kind,currency:e.currency};if(e.kind==="total")n.row=B(e.row);else if(e.kind==="grouped"){const s=e.groups.slice().sort((l,r)=>l.label.localeCompare(r.label));n.groups=s.map(l=>({label:l.label,items:l.items.map(B)}))}else if(e.kind==="timeSeries")n.timeSeries=e.timeSeries.map(W);else if(e.kind==="timeSeriesGrouped"){const s=e.groups.slice().sort((l,r)=>l.label.localeCompare(r.label));n.groups=s.map(l=>({label:l.label,items:l.items.map(W)}))}return JSON.stringify(n,null,2)}function B(e){const n={label:e.label,costAmount:e.costAmount};return e.deltaCost!==void 0&&(n.deltaCost=e.deltaCost),e.inputTokens!==void 0&&(n.inputTokens=e.inputTokens),e.outputTokens!==void 0&&(n.outputTokens=e.outputTokens),e.cacheReadTokens!==void 0&&(n.cacheReadTokens=e.cacheReadTokens),e.cacheWriteTokens!==void 0&&(n.cacheWriteTokens=e.cacheWriteTokens),e.linesAdded!==void 0&&(n.linesAdded=e.linesAdded),e.linesRemoved!==void 0&&(n.linesRemoved=e.linesRemoved),(e.linesAdded!==void 0||e.linesRemoved!==void 0)&&(n.linesNet=(e.linesAdded??0)-(e.linesRemoved??0)),n}function W(e){const n={bucket:e.bucket,costAmount:e.costAmount,deltaCost:e.deltaCost};return e.inputTokens!==void 0&&(n.inputTokens=e.inputTokens),e.outputTokens!==void 0&&(n.outputTokens=e.outputTokens),e.cacheReadTokens!==void 0&&(n.cacheReadTokens=e.cacheReadTokens),e.cacheWriteTokens!==void 0&&(n.cacheWriteTokens=e.cacheWriteTokens),e.linesAdded!==void 0&&(n.linesAdded=e.linesAdded),e.linesRemoved!==void 0&&(n.linesRemoved=e.linesRemoved),(e.linesAdded!==void 0||e.linesRemoved!==void 0)&&(n.linesNet=(e.linesAdded??0)-(e.linesRemoved??0)),n}function S(e){return`$${e.toFixed(2)}`}function R(e){const n=e.linesAdded??0,s=e.linesRemoved??0;return n-s}function A(e){return`${e>0?"+":""}${e.toLocaleString()}`}function h(e){let n=0;return e.inputTokens!==void 0&&(n+=e.inputTokens),e.outputTokens!==void 0&&(n+=e.outputTokens),n}function z(e,n){return e.length>=n?e:e+" ".repeat(n-e.length)}function _(e){let n=0;for(const s of e)for(const l of s.items)n+=l.costAmount;return n}export{F as renderJson,E as renderText};
@@ -1 +1 @@
1
- import{createInterface as j}from"node:readline";import{createReadStream as S,statSync as x}from"node:fs";import{resolve as N}from"node:path";import{logger as _}from"../util/log.js";import{sessionsDir as $}from"./session-store.js";import{filetypeForPath as D}from"./language.js";const R=_("cost/history-stream");function M(n){if(!n||typeof n!="object"||Array.isArray(n))return;const t=n["hydra-acp"];if(!t||typeof t!="object"||Array.isArray(t))return;const e=t.cumulativeCost;return typeof e=="number"?e:void 0}function L(n){const t=M(n._meta),e=n.cost??void 0,c=t!==void 0?t:typeof e?.amount=="number"?e.amount:void 0;if(c===void 0)return;const f=typeof e?.currency=="string"?e.currency:void 0;return{amount:c,currency:f}}function O(n){const t=n.recordedAt;return typeof t=="number"?new Date(t).toISOString():typeof t=="string"?t:""}async function*q(n){const t=Array.isArray(n)?n:[n];for(const e of t){const c=N($(),e.sessionId,"history.jsonl");try{x(c)}catch(y){const o=y;if(o.code==="ENOENT"){R.debug(`no history for ${e.sessionId}`);continue}R.debug(`stat failed for ${c}: ${o.message}`);continue}const f=S(c,{encoding:"utf8"}),h=j({input:f,crlfDelay:1/0}),m=new Map;try{for await(const y of h){if(y.length===0)continue;let o;try{o=JSON.parse(y)}catch{R.debug(`malformed JSON in history.jsonl for ${e.sessionId}`);continue}if(!o||typeof o!="object"||Array.isArray(o))continue;const r=o;if(r.method!=="session/update")continue;const a=r.params??void 0;if(!a||typeof a!="object"||Array.isArray(a))continue;const i=a.update??void 0;if(!i||typeof i!="object"||Array.isArray(i)||i.sessionUpdate!=="usage_update")continue;const u=O(r),p=L(i);if(p===void 0)continue;const w=p.currency??"",b=p.amount,g=m.get(e.sessionId)??0,d=Math.max(0,b-g);m.set(e.sessionId,b);const s=i.usage??void 0;let A,k,l,v;if(s&&typeof s=="object"&&!Array.isArray(s)){const I=s.inputTokens;typeof I=="number"&&(A=I);const T=s.outputTokens;typeof T=="number"&&(k=T);const E=s.cacheReadInputTokens;typeof E=="number"&&(l=E);const C=s.cacheCreationInputTokens;typeof C=="number"&&(v=C)}yield{sessionId:e.sessionId,ts:u,deltaCost:d,cumulativeCost:b,currency:w,...A!==void 0&&{inputTokens:A},...k!==void 0&&{outputTokens:k},...l!==void 0&&{cacheReadTokens:l},...v!==void 0&&{cacheWriteTokens:v}}}}finally{h.close(),f.destroy()}}}function J(n){if(n.length===0)return 0;let t=1;for(let e=0;e<n.length;e++)n.charCodeAt(e)===10&&t++;return n.charCodeAt(n.length-1)===10&&t--,t}async function*z(n){const t=Array.isArray(n)?n:[n];for(const e of t){const c=N($(),e.sessionId,"history.jsonl");try{x(c)}catch(o){const r=o;if(r.code==="ENOENT")continue;R.debug(`stat failed for ${c}: ${r.message}`);continue}const f=S(c,{encoding:"utf8"}),h=j({input:f,crlfDelay:1/0}),m=new Map,y=[];try{for await(const o of h){if(o.length===0)continue;let r;try{r=JSON.parse(o)}catch{continue}if(!r||typeof r!="object"||Array.isArray(r))continue;const a=r;if(a.method!=="session/update")continue;const i=a.params??void 0;if(!i||typeof i!="object"||Array.isArray(i))continue;const u=i.update??void 0;if(!u||typeof u!="object"||Array.isArray(u)||u.sessionUpdate!=="tool_call"&&u.sessionUpdate!=="tool_call_update")continue;const p=u.content;if(!Array.isArray(p)||p.length===0)continue;const w=typeof u.toolCallId=="string"?u.toolCallId:"";if(w==="")continue;const b=O(a);for(const g of p){if(!g||typeof g!="object"||Array.isArray(g))continue;const d=g;if(d.type!=="diff")continue;const s=typeof d.path=="string"?d.path:"";if(s==="")continue;const A=typeof d.oldText=="string"?d.oldText:"",k=typeof d.newText=="string"?d.newText:"",l=`${w}|${s}`;m.has(l)||y.push(l),m.set(l,{sessionId:e.sessionId,ts:b,path:s,filetype:D(s),linesAdded:J(k),linesRemoved:J(A)})}}}finally{h.close(),f.destroy()}for(const o of y){const r=m.get(o);r!==void 0&&(yield r)}}}export{z as streamHistoryEditEvents,q as streamHistoryEvents};
1
+ import{createInterface as j}from"node:readline";import{createReadStream as S,statSync as x}from"node:fs";import{resolve as N}from"node:path";import{logger as J}from"../util/log.js";import{sessionsDir as _}from"./session-store.js";import{filetypeForPath as M}from"./language.js";import{fetchToolBlob as P}from"./daemon-client.js";const I=J("cost/history-stream");function B(t){if(!t||typeof t!="object"||Array.isArray(t))return;const n=t["hydra-acp"];if(!n||typeof n!="object"||Array.isArray(n))return;const e=n.cumulativeCost;return typeof e=="number"?e:void 0}function L(t){const n=B(t._meta),e=t.cost??void 0,i=n!==void 0?n:typeof e?.amount=="number"?e.amount:void 0;if(i===void 0)return;const a=typeof e?.currency=="string"?e.currency:void 0;return{amount:i,currency:a}}function $(t){const n=t.recordedAt;return typeof n=="number"?new Date(n).toISOString():typeof n=="string"?n:""}async function*K(t){const n=Array.isArray(t)?t:[t];for(const e of n){const i=N(_(),e.sessionId,"history.jsonl");try{x(i)}catch(f){const o=f;if(o.code==="ENOENT"){I.debug(`no history for ${e.sessionId}`);continue}I.debug(`stat failed for ${i}: ${o.message}`);continue}const a=S(i,{encoding:"utf8"}),b=j({input:a,crlfDelay:1/0}),m=new Map;try{for await(const f of b){if(f.length===0)continue;let o;try{o=JSON.parse(f)}catch{I.debug(`malformed JSON in history.jsonl for ${e.sessionId}`);continue}if(!o||typeof o!="object"||Array.isArray(o))continue;const r=o;if(r.method!=="session/update")continue;const d=r.params??void 0;if(!d||typeof d!="object"||Array.isArray(d))continue;const c=d.update??void 0;if(!c||typeof c!="object"||Array.isArray(c)||c.sessionUpdate!=="usage_update")continue;const u=$(r),y=L(c);if(y===void 0)continue;const w=y.currency??"",h=y.amount,g=m.get(e.sessionId)??0,p=Math.max(0,h-g);m.set(e.sessionId,h);const s=c.usage??void 0;let A,k,l,R;if(s&&typeof s=="object"&&!Array.isArray(s)){const T=s.inputTokens;typeof T=="number"&&(A=T);const v=s.outputTokens;typeof v=="number"&&(k=v);const E=s.cacheReadInputTokens;typeof E=="number"&&(l=E);const C=s.cacheCreationInputTokens;typeof C=="number"&&(R=C)}yield{sessionId:e.sessionId,ts:u,deltaCost:p,cumulativeCost:h,currency:w,...A!==void 0&&{inputTokens:A},...k!==void 0&&{outputTokens:k},...l!==void 0&&{cacheReadTokens:l},...R!==void 0&&{cacheWriteTokens:R}}}}finally{b.close(),a.destroy()}}}async function O(t,n){if(typeof n=="string")return n;if(n&&typeof n=="object"&&!Array.isArray(n)){const e=n.__hydraBlob;if(typeof e=="string")return await P(t,e)??""}return""}function D(t){if(t.length===0)return 0;let n=1;for(let e=0;e<t.length;e++)t.charCodeAt(e)===10&&n++;return t.charCodeAt(t.length-1)===10&&n--,n}async function*Q(t){const n=Array.isArray(t)?t:[t];for(const e of n){const i=N(_(),e.sessionId,"history.jsonl");try{x(i)}catch(o){const r=o;if(r.code==="ENOENT")continue;I.debug(`stat failed for ${i}: ${r.message}`);continue}const a=S(i,{encoding:"utf8"}),b=j({input:a,crlfDelay:1/0}),m=new Map,f=[];try{for await(const o of b){if(o.length===0)continue;let r;try{r=JSON.parse(o)}catch{continue}if(!r||typeof r!="object"||Array.isArray(r))continue;const d=r;if(d.method!=="session/update")continue;const c=d.params??void 0;if(!c||typeof c!="object"||Array.isArray(c))continue;const u=c.update??void 0;if(!u||typeof u!="object"||Array.isArray(u)||u.sessionUpdate!=="tool_call"&&u.sessionUpdate!=="tool_call_update")continue;const y=u.content;if(!Array.isArray(y)||y.length===0)continue;const w=typeof u.toolCallId=="string"?u.toolCallId:"";if(w==="")continue;const h=$(d);for(const g of y){if(!g||typeof g!="object"||Array.isArray(g))continue;const p=g;if(p.type!=="diff")continue;const s=typeof p.path=="string"?p.path:"";if(s==="")continue;const A=await O(e.sessionId,p.oldText),k=await O(e.sessionId,p.newText),l=`${w}|${s}`;m.has(l)||f.push(l),m.set(l,{sessionId:e.sessionId,ts:h,path:s,filetype:M(s),linesAdded:D(k),linesRemoved:D(A)})}}}finally{b.close(),a.destroy()}for(const o of f){const r=m.get(o);r!==void 0&&(yield r)}}}export{Q as streamHistoryEditEvents,K as streamHistoryEvents};
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as $}from"node:fs";import{dirname as C,resolve as U}from"node:path";import{fileURLToPath as P}from"node:url";import{loadConfig as N}from"./config.js";import{BudgeterBridge as x}from"./bridge.js";import{stateFilePath as I}from"./paths.js";import{DEFAULT_RULE as M}from"./rule.js";import{deleteStateFile as G}from"./tracker.js";import{logger as H,setDebug as W}from"./util/log.js";import{scanSessions as j,enrichSessionsWithLoc as A}from"./cost/session-store.js";import{listSessionsFromDaemon as B,fetchUsageEventsFromDaemon as _}from"./cost/daemon-client.js";import{streamHistoryEditEvents as J}from"./cost/history-stream.js";import{aggregate as Y,applyFilters as q,parseSince as V}from"./cost/aggregate.js";import{renderText as K,renderJson as Z}from"./cost/format.js";const D=H("main");function O(){try{const t=C(P(import.meta.url));return JSON.parse($(U(t,"../package.json"),"utf8")).version??"unknown"}catch{return"unknown"}}function z(){G(I()),process.stdout.write(`hydra-acp-budgeter accumulated cost reset
3
- `)}const Q=`Usage: hydra budgeter usage [OPTIONS]
2
+ import{readFileSync as U}from"node:fs";import{dirname as F,resolve as N}from"node:path";import{fileURLToPath as P}from"node:url";import{loadConfig as H}from"./config.js";import{BudgeterBridge as M}from"./bridge.js";import{DEFAULT_RULE as W}from"./rule.js";import{logger as j,setDebug as G}from"./util/log.js";import{scanSessions as _,enrichSessionsWithLoc as B}from"./cost/session-store.js";import{listSessionsFromDaemon as A,fetchUsageEventsFromDaemon as J}from"./cost/daemon-client.js";import{streamHistoryEditEvents as Y}from"./cost/history-stream.js";import{aggregate as q,applyFilters as V,parseSince as K}from"./cost/aggregate.js";import{renderText as Z,renderJson as z}from"./cost/format.js";const D=j("main");function L(){try{const e=F(P(import.meta.url));return JSON.parse(U(N(e,"../package.json"),"utf8")).version??"unknown"}catch{return"unknown"}}function Q(){process.stderr.write(`hydra-acp-budgeter: 'reset' as a CLI subcommand is no longer supported.
3
+ Run \`/hydra hydra-acp-budgeter reset\` in a live hydra session instead.
4
+ Cost state is now persisted per-session in meta.json (extension_state).
5
+ `),process.exit(2)}const X=`Usage: hydra budgeter usage [OPTIONS]
4
6
 
5
7
  Options:
6
8
  --since <date|duration> Only include sessions updated after this date (e.g. 7d, 2024-01-01)
@@ -14,18 +16,19 @@ Options:
14
16
  every session; <name> shows passive mirrors imported from
15
17
  that host.
16
18
  --min <N> Drop sessions whose active-metric value is <= N (default: 0)
17
- --histogram Show an ASCII histogram bar next to each row (implies --bucket hour if no bucket given)
19
+ --histogram Show an ASCII histogram bar next to each row (default on; implies --bucket hour if no bucket given)
20
+ --no-histogram Collapse to a single total row (also drops the default hourly bucket)
18
21
  --metric <cost|tokens|loc> Display metric (default: cost). "loc" counts net lines of code
19
22
  from Edit/Write tool diffs in session history.
20
23
  --json Output as JSON
21
- --help Show this help message`;async function R(t){if(t.includes("--help")){process.stdout.write(Q+`
22
- `);return}let r,n,i,u,l,p=!1,f,c=!1,o,g=!1,h;for(let e=0;e<t.length;e++){const s=t[e];if(s!==void 0){if(s==="--since")e+=1,r=t[e];else if(s==="--bucket")e+=1,n=t[e];else if(s==="--by")e+=1,i=t[e];else if(s==="--depth")e+=1,u=t[e];else if(s==="--dir")e+=1,l=t[e];else if(s==="--interactive")p=!0;else if(s==="--min")e+=1,f=t[e];else if(s==="--histogram")c=!0;else if(s==="--metric")e+=1,o=t[e];else if(s==="--json")g=!0;else if(s==="--host")e+=1,h=t[e];else if(s.startsWith("--host="))h=s.slice(7);else if(s.startsWith("--"))throw new Error(`Unknown option: ${s}
24
+ --help Show this help message`;async function T(e){if(e.includes("--help")){process.stdout.write(X+`
25
+ `);return}let r,n="hour",i,d,l,w=!1,f,u=!0,o,b=!1,h,m=!1,p=!1;for(let t=0;t<e.length;t++){const s=e[t];if(s!==void 0){if(s==="--since")t+=1,r=e[t];else if(s==="--bucket")t+=1,n=e[t],m=!0;else if(s==="--by")t+=1,i=e[t];else if(s==="--depth")t+=1,d=e[t];else if(s==="--dir")t+=1,l=e[t];else if(s==="--interactive")w=!0;else if(s==="--min")t+=1,f=e[t];else if(s==="--histogram")u=!0,p=!0;else if(s==="--no-histogram")u=!1,p=!0;else if(s==="--metric")t+=1,o=e[t];else if(s==="--json")b=!0;else if(s==="--host")t+=1,h=e[t];else if(s.startsWith("--host="))h=s.slice(7);else if(s.startsWith("--"))throw new Error(`Unknown option: ${s}
23
26
  Run "hydra budgeter usage --help" for usage.`)}}if(o!==void 0&&o!=="cost"&&o!=="tokens"&&o!=="loc")throw new Error(`Invalid --metric value. Must be 'cost', 'tokens', or 'loc'.
24
27
  Run "hydra budgeter usage --help" for usage.`);const a=o==="loc";if(i!==void 0&&!new Set(["dir","session","model","agent","filetype"]).has(i))throw new Error(`Invalid --by value: ${i}. Must be one of: dir, session, model, agent, filetype.
25
28
  Run "hydra budgeter usage --help" for usage.`);if(i==="filetype"&&!a)throw new Error(`--by filetype requires --metric loc.
26
- Run "hydra budgeter usage --help" for usage.`);let y;if(r!==void 0)try{y=V(r)}catch(e){const s=e;throw new Error(`Invalid --since value: ${s.message}
27
- Run "hydra budgeter usage --help" for usage.`)}t.length===0&&(c=!0),c&&n===void 0&&(n="hour");let d=y;if(d===void 0&&n!==void 0){const e=new Date;n==="hour"?(e.setHours(e.getHours()-24),d=e):n==="day"?(e.setDate(e.getDate()-30),d=e):n==="week"?(e.setMonth(e.getMonth()-6),d=e):n==="month"&&(e.setFullYear(e.getFullYear()-2),d=e)}const w=p?!0:void 0,m=o==="tokens",v=f!==void 0?parseFloat(f):void 0,b=await B()??j();(a||i==="filetype")&&await A(b);const E=q(b,{since:d,dir:l,interactive:w,min:v,minMetric:a?"loc":m?"tokens":"cost",host:h??"local"});let k;if(n!==void 0){const e=await _();e!==void 0&&(k=e.map(s=>({sessionId:s.sessionId,ts:s.ts,deltaCost:0,cumulativeCost:s.costCumulative,currency:s.costCurrency,inputTokens:s.contextTokens})))}let S;if(a&&n!==void 0){const e=[];for await(const s of J(E))e.push(s);S=e}const F=u!==void 0?parseInt(u,10):void 0,T=Y(E,k,{by:i,depth:F,bucket:n,since:d,interactive:w,dir:l,tokens:m,loc:a,min:v},S);if(g)process.stdout.write(Z(T)+`
28
- `);else{const e=K(T,{histogram:c,tokens:m,loc:a});process.stdout.write(e)}}async function X(){const t=N();W(t.debug);const r=I(),n=new x({daemonWsUrl:t.hydraWsUrl,token:t.hydraToken,softLimit:t.softLimit,hardLimit:t.hardLimit,currency:t.currency,rule:M,statePath:r});n.start();const i=u=>{D.info(`${u} received \u2014 shutting down`),n.stop(),setTimeout(()=>process.exit(0),200).unref()};process.on("SIGINT",()=>i("SIGINT")),process.on("SIGTERM",()=>i("SIGTERM")),D.info(`hydra-acp-budgeter up; daemon=${t.hydraDaemonUrl} soft=${t.softLimit} hard=${t.hardLimit} ${t.currency} state=${r}`)}function ee(){process.stdout.write(`hydra-acp-budgeter ${O()}
29
+ Run "hydra budgeter usage --help" for usage.`);let g;if(r!==void 0)try{g=K(r)}catch(t){const s=t;throw new Error(`Invalid --since value: ${s.message}
30
+ Run "hydra budgeter usage --help" for usage.`)}i!==void 0&&(m||(n=void 0),p||(u=!1)),!u&&!m&&(n=void 0);let c;if(g!==void 0)c=g;else{const t=new Date;n==="day"?t.setDate(t.getDate()-30):n==="week"?t.setMonth(t.getMonth()-6):n==="month"?t.setFullYear(t.getFullYear()-2):t.setHours(t.getHours()-24),c=t}const v=w?!0:void 0,y=o==="tokens",k=f!==void 0?parseFloat(f):void 0,E=await A()??_();(a||i==="filetype")&&await B(E);const S=V(E,{since:c,dir:l,interactive:v,min:k,minMetric:a?"loc":y?"tokens":"cost",host:h??"local"});let R;if(n!==void 0){const t=await J();t!==void 0&&(R=t.map(s=>({sessionId:s.sessionId,ts:s.ts,deltaCost:0,cumulativeCost:s.costCumulative,currency:s.costCurrency,inputTokens:s.contextTokens})))}let I;if(a&&n!==void 0){const t=[];for await(const s of Y(S))t.push(s);I=t}const C=d!==void 0?parseInt(d,10):void 0,$=q(S,R,{by:i,depth:C,bucket:n,since:c,interactive:v,dir:l,tokens:y,loc:a,min:k},I),x=ee({sinceRaw:r,effectiveSince:c,bucket:n});if(b)process.stdout.write(z($)+`
31
+ `);else{const t=Z($,{histogram:u,tokens:y,loc:a,windowLabel:x});process.stdout.write(t)}}function ee(e){if(e.sinceRaw!==void 0){const r=te(e.sinceRaw);return r!==void 0?`last ${r}`:`since ${e.sinceRaw}`}return e.bucket==="day"?"last 30 days":e.bucket==="week"?"last 6 months":e.bucket==="month"?"last 2 years":e.bucket==="hour"?"last 24 hours":"last 24 hours (default; pass --since to widen)"}function te(e){const r=e.match(/^(\d+)\s*([dhmwy])$/i);if(r===null)return;let n=parseInt(r[1]??"0",10),i=(r[2]??"").toLowerCase();i==="h"&&n%24===0&&(n=n/24,i="d"),i==="d"&&n%7===0&&n<30&&(n=n/7,i="w"),i==="m"&&n%12===0&&(n=n/12,i="y");const d={h:"hour",d:"day",w:"week",m:"month",y:"year"}[i];if(d!==void 0)return`${n} ${d}${n===1?"":"s"}`}async function se(){const e=H();G(e.debug);const r=new M({daemonWsUrl:e.hydraWsUrl,daemonHttpBase:e.hydraDaemonUrl,token:e.hydraToken,softLimit:e.softLimit,hardLimit:e.hardLimit,currency:e.currency,rule:W});r.start();const n=i=>{D.info(`${i} received \u2014 shutting down`),r.stop(),setTimeout(()=>process.exit(0),200).unref()};process.on("SIGINT",()=>n("SIGINT")),process.on("SIGTERM",()=>n("SIGTERM")),D.info(`hydra-acp-budgeter up; daemon=${e.hydraDaemonUrl} soft=${e.softLimit} hard=${e.hardLimit} ${e.currency} state=per-session (extension_state)`)}function ne(){process.stdout.write(`hydra-acp-budgeter ${L()}
29
32
 
30
33
  Usage:
31
34
  hydra budgeter [usage] <flags> Report historical cost/usage across sessions
@@ -34,6 +37,6 @@ Usage:
34
37
  Flags:
35
38
  -v, --version Print version and exit
36
39
  -h, --help Show this help
37
- `)}async function te(){const t=process.argv.slice(2);if(t.includes("--version")||t.includes("-v")){process.stdout.write(`hydra-acp-budgeter ${O()}
38
- `);return}if((t[0]==="help"||t.includes("--help")||t.includes("-h"))&&t[0]!=="usage"&&t[0]!=="cost"){ee();return}if(t[0]==="reset"){z();return}if(t[0]==="usage"||t[0]==="cost"){await R(t.slice(1));return}if(t[0]==="run"||process.env.HYDRA_ACP_TOKEN){await X();return}await R(t)}te().catch(t=>{process.stderr.write(`hydra-acp-budgeter: ${t.message}
40
+ `)}async function ie(){const e=process.argv.slice(2);if(e.includes("--version")||e.includes("-v")){process.stdout.write(`hydra-acp-budgeter ${L()}
41
+ `);return}if((e[0]==="help"||e.includes("--help")||e.includes("-h"))&&e[0]!=="usage"&&e[0]!=="cost"){ne();return}if(e[0]==="reset"){Q();return}if(e[0]==="usage"||e[0]==="cost"){await T(e.slice(1));return}if(e[0]==="run"||process.env.HYDRA_ACP_TOKEN){await se();return}await T(e)}ie().catch(e=>{process.stderr.write(`hydra-acp-budgeter: ${e.message}
39
42
  `),process.exit(1)});
package/dist/tracker.js CHANGED
@@ -1 +1 @@
1
- import{mkdirSync as l,readFileSync as d,renameSync as p,unlinkSync as m,writeFileSync as y}from"node:fs";import{dirname as g}from"node:path";import{logger as b}from"./util/log.js";const n=b("tracker");class x{constructor(t){this.opts=t;t.statePath&&this.loadFromDisk()}opts;sessions=new Map;currentState="ok";lastWrittenJson="";applyUsageUpdate(t,e){n.debug(`usage_update session=${t.slice(0,12)} raw=${JSON.stringify(e).slice(0,200)}`);const s=S(e);if(s===void 0)return n.debug(`readCost returned undefined for session=${t.slice(0,12)}`),this.snapshotFor(t);const i=this.sessions.get(t)??{cost:0,baseline:0,currency:void 0},o={cost:Math.max(i.cost,s.amount),baseline:i.baseline,currency:s.currency??i.currency};return this.sessions.set(t,o),this.persist(),this.snapshotFor(t)}snapshotFor(t){const e=this.sessions.get(t),s=this.totalCost();return{total:s,perSession:e?Math.max(0,e.cost-e.baseline):0,currency:e?.currency??this.opts.currency,soft:this.opts.softLimit,hard:this.opts.hardLimit,state:f(s,this.opts.softLimit,this.opts.hardLimit)}}consumeStateTransition(){const t=f(this.totalCost(),this.opts.softLimit,this.opts.hardLimit);if(h(t)>h(this.currentState))return this.currentState=t,t;t!==this.currentState&&(this.currentState=t)}get state(){return this.currentState}reset(){this.baselineFromCurrent(),this.persist()}baselineFromCurrent(){for(const[t,e]of this.sessions)this.sessions.set(t,{...e,baseline:e.cost});this.currentState="ok"}adoptFromDisk(){if(!this.opts.statePath)return!1;let t;try{t=d(this.opts.statePath,"utf8")}catch(s){const i=s;return i.code==="ENOENT"?this.sessions.size===0?!1:(n.info("state file removed externally \u2014 baselining from current totals"),this.baselineFromCurrent(),this.lastWrittenJson="",!0):(n.warn(`read state failed: ${i.message}`),!1)}if(t===this.lastWrittenJson)return!1;const e=this.applyPersisted(t,!1);return e&&(this.lastWrittenJson=t,n.info(`adopted state from disk (total=${this.totalCost().toFixed(2)})`)),e}loadFromDisk(){if(!this.opts.statePath)return;let t;try{t=d(this.opts.statePath,"utf8")}catch(e){const s=e;s.code!=="ENOENT"&&n.warn(`read state failed: ${s.message}; starting fresh`);return}this.applyPersisted(t)&&(this.lastWrittenJson=t,n.info(`loaded state from ${this.opts.statePath} (total=${this.totalCost().toFixed(2)})`))}applyPersisted(t,e=!0){let s;try{s=JSON.parse(t)}catch(c){return n.warn(`state file malformed: ${c.message}; ignoring`),!1}if(!s||typeof s!="object"||Array.isArray(s))return n.warn("state file is not an object; ignoring"),!1;const o=s.sessions??{};if(typeof o!="object"||Array.isArray(o))return n.warn("state.sessions is not an object; ignoring"),!1;this.sessions.clear();for(const[c,u]of Object.entries(o))if(u&&typeof u=="object"&&typeof u.cost=="number"){const a=u;this.sessions.set(c,{cost:a.cost,baseline:typeof a.baseline=="number"?a.baseline:0,currency:typeof a.currency=="string"?a.currency:void 0})}return e&&(this.currentState=f(this.totalCost(),this.opts.softLimit,this.opts.hardLimit)),!0}persist(){if(!this.opts.statePath)return;const t={version:1,sessions:Object.fromEntries(this.sessions)},e=JSON.stringify(t,null,2);if(e!==this.lastWrittenJson)try{l(g(this.opts.statePath),{recursive:!0});const s=`${this.opts.statePath}.tmp`;y(s,e,{encoding:"utf8",mode:384}),p(s,this.opts.statePath),this.lastWrittenJson=e}catch(s){n.warn(`persist failed: ${s.message}`)}}totalCost(){let t=0;for(const e of this.sessions.values())t+=Math.max(0,e.cost-e.baseline);return t}}function E(r){try{return m(r),!0}catch(t){if(t.code==="ENOENT")return!1;throw t}}function f(r,t,e){return r>=e?"hard":r>=t?"soft":"ok"}function h(r){return r==="hard"?2:r==="soft"?1:0}function S(r){const t=k(r._meta),e=r.cost??void 0,s=t!==void 0?t:typeof e?.amount=="number"?e.amount:void 0;if(s===void 0)return;const i=typeof e?.currency=="string"?e.currency:void 0;return{amount:s,currency:i}}function k(r){if(!r||typeof r!="object"||Array.isArray(r))return;const t=r["hydra-acp"];if(!t||typeof t!="object"||Array.isArray(t))return;const e=t.cumulativeCost;return typeof e=="number"?e:void 0}export{x as CostTracker,E as deleteStateFile};
1
+ import{logger as c}from"./util/log.js";const i=c("tracker");class m{constructor(t){this.opts=t}opts;sessions=new Map;confirmedAbsent=new Set;hydrating=new Map;currentState="ok";async hydrateAll(){if(!this.opts.store)return;let t;try{t=await this.opts.store.listSessionIds()}catch(e){i.warn(`boot hydration failed to list sessions: ${e.message} \u2014 continuing with lazy loading`);return}for(const e of t)if(!(this.sessions.has(e)||this.confirmedAbsent.has(e)))try{const s=await this.opts.store.get(e);s?this.sessions.set(e,s):this.confirmedAbsent.add(e)}catch(s){i.warn(`boot hydration failed for session ${e.slice(0,12)}: ${s.message}`)}this.currentState=o(this.totalCost(),this.opts.softLimit,this.opts.hardLimit),i.info(`hydrated ${this.sessions.size} session(s), total=${this.totalCost().toFixed(2)}`)}applyUsageUpdate(t,e){i.debug(`usage_update session=${t.slice(0,12)} raw=${JSON.stringify(e).slice(0,200)}`);const s=d(e);if(s===void 0)return i.debug(`readCost returned undefined for session=${t.slice(0,12)}`),this.snapshotFor(t);const n=this.sessions.get(t)??{cost:0,baseline:0,currency:void 0},a={cost:Math.max(n.cost,s.amount),baseline:n.baseline,currency:s.currency??n.currency};return this.sessions.set(t,a),this.confirmedAbsent.delete(t),this.persistOne(t,a),this.snapshotFor(t)}async ensureHydrated(t){if(!this.opts.store||this.sessions.has(t)||this.confirmedAbsent.has(t))return;const e=this.hydrating.get(t);if(e)return e;const s=(async()=>{try{const n=await this.opts.store.get(t);n?this.sessions.set(t,n):this.confirmedAbsent.add(t)}catch(n){i.warn(`hydrate session ${t.slice(0,12)} failed: ${n.message}`)}finally{this.hydrating.delete(t)}})();return this.hydrating.set(t,s),s}snapshotFor(t){const e=this.sessions.get(t),s=this.totalCost();return{total:s,perSession:e?Math.max(0,e.cost-e.baseline):0,currency:e?.currency??this.opts.currency,soft:this.opts.softLimit,hard:this.opts.hardLimit,state:o(s,this.opts.softLimit,this.opts.hardLimit)}}consumeStateTransition(){const t=o(this.totalCost(),this.opts.softLimit,this.opts.hardLimit);if(u(t)>u(this.currentState))return this.currentState=t,t;t!==this.currentState&&(this.currentState=t)}get state(){return this.currentState}reset(){for(const[t,e]of this.sessions){const s={...e,baseline:e.cost};this.sessions.set(t,s),this.persistOne(t,s)}this.currentState="ok"}async persistOne(t,e){if(this.opts.store)try{await this.opts.store.set(t,e)}catch(s){i.warn(`persist ${t.slice(0,12)} failed: ${s.message}`)}}totalCost(){let t=0;for(const e of this.sessions.values())t+=Math.max(0,e.cost-e.baseline);return t}}function o(r,t,e){return r>=e?"hard":r>=t?"soft":"ok"}function u(r){return r==="hard"?2:r==="soft"?1:0}function d(r){const t=h(r._meta),e=r.cost??void 0,s=t!==void 0?t:typeof e?.amount=="number"?e.amount:void 0;if(s===void 0)return;const n=typeof e?.currency=="string"?e.currency:void 0;return{amount:s,currency:n}}function h(r){if(!r||typeof r!="object"||Array.isArray(r))return;const t=r["hydra-acp"];if(!t||typeof t!="object"||Array.isArray(t))return;const e=t.cumulativeCost;return typeof e=="number"?e:void 0}export{m as CostTracker};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/budgeter",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Cost-budget transformer extension for hydra-acp — warns on soft limit, rejects further prompts/sessions on hard limit.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/dist/paths.js DELETED
@@ -1 +0,0 @@
1
- import{homedir as t}from"node:os";import{resolve as r}from"node:path";function e(){return process.env.HYDRA_ACP_HOME??r(t(),".hydra-acp")}function i(){return r(e(),"budgeter-cost.json")}export{i as stateFilePath};