@adhd/agent-plugin-budget 0.2.4 → 0.2.5

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/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.2.5 (2026-10-07)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **agent-engine-orchestrator:** un-weld agent from provider; resolveSelection chain + session-held binding + fail-closed NO_PROVIDER_SELECTED (5ad2cf37 D-G(2)) ([b5f3f6d8](https://github.com/PseudoSky/adhd/commit/b5f3f6d8))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - pseudosky
10
+
1
11
  ## 0.2.4 (2026-10-06)
2
12
 
3
13
  ### 🩹 Fixes
package/index.cjs CHANGED
@@ -30,4 +30,4 @@ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{val
30
30
  FROM task_usage
31
31
  WHERE created_at >= ${o}
32
32
  ${i?t.sql` AND task_id != ${i}`:t.sql``}
33
- `)),s?.total??0}catch{return 0}}costFromTotals(e){return e.uncachedInputTokens*this.costPerInput+e.cacheReadTokens*this.costPerCacheRead+e.cacheCreationTokens*this.costPerCacheWrite+e.outputTokens*this.costPerOutput}buildSnapshot(e,t,n,i,a,o,s=0){let c={};c.inputTokens=t.inputTokens,c.outputTokens=t.outputTokens,c.calls=t.modelCalls,c.wallClock=Date.now()-t.startedAtMs,c.modelMs=t.totalModelMs,c.context=Math.max(t.peakContextTokens,s),c.errors=t.errors,c.consecutiveErrors=t.consecutiveErrors,c.cost=this.costFromTotals(t);let l=new Set,u=new Map;for(let t of e){let e=t.scope??o??`task`;if(e!==`task`&&l.add(e),t.window){let n=`${e}:${t.window}`;u.has(n)||u.set(n,{scope:e,windowMs:r(t.window)})}}for(let e of l){let t=this.queryScopeTotals(n,i,a,e);c[`${e}:inputTokens`]=t.inputTokens,c[`${e}:outputTokens`]=t.outputTokens,c[`${e}:calls`]=t.modelCalls,c[`${e}:context`]=Math.max(t.peakContextTokens,s),c[`${e}:cost`]=this.costFromTotals(t)}for(let[e,{scope:t,windowMs:r}]of u){let o=``;t===`session`?o=i??``:t===`agent`&&(o=a??``),c[e]=this.queryWindowTokens(t,o,r,n)}return c}getSnapshotValue(e,t,n){let r=t.scope??n??`task`,i;i=t.window||r===`task`?``:`${r}:`;let a;switch(t.field){case`inputTokens`:a=e[`${i}inputTokens`]??e.inputTokens;break;case`outputTokens`:a=e[`${i}outputTokens`]??e.outputTokens;break;case`context`:a=e[`${i}context`]??e.context;break;case`calls`:a=e[`${i}calls`]??e.calls;break;case`wallClock`:a=e.wallClock;break;case`modelMs`:a=e.modelMs;break;case`cost`:a=e[`${i}cost`]??e.cost;break;case`toolCalls`:a=0;break;case`errors`:a=e.errors;break;case`consecutiveErrors`:a=e.consecutiveErrors;break;default:a=0}return t.window&&(a+=e[`${r}:${t.window}`]??0),a}async emitBudgetEvent(e,t,n,r,i,a){this.hooks&&await this.hooks.emit(e,{executionContext:t,field:n.field,maximum:i,current:r,message:a??n.message??`${n.field} limit is ${i}, current value is ${Math.round(r)}`})}resolveCapLimit(e,t){if(e.maximum!==void 0)return e.maximum;let r=t.agentDefinition.provider;return Math.floor((0,n.contextWindowFor)(r.model)*(e.contextWindowFraction??0))}async evaluateCap(e,t,n,r,i){let a=this.resolveCapLimit(e,n),o=this.getSnapshotValue(t,e,i);if(!(o<a)){if((e.mode??r??`warning`)===`warning`){await this.emitBudgetEvent(`budget:warning`,n,e,o,a);return}throw await this.emitBudgetEvent(`budget:block`,n,e,o,a),g(e.field,a,o,e.message)}}async enforcePreModel(e){let{taskId:t,sessionId:n,agentName:r}=e.executionContext,i=e.executionContext.agentDefinition?.provider?.type??`unknown`,o=this.accumulators.get(t);if(!o)return;let{caps:s,mode:c,scope:l}=this.resolveCaps(r,i),u=s.filter(e=>!a.has(e.field));if(u.length===0)return;let d=this.buildSnapshot(u,o,t,n,r,l,v(e.messages,e.tools));for(let t of u)await this.evaluateCap(t,d,e.executionContext,c,l)}async enforcePreTool(e){let{toolName:t,callId:n,executionContext:r}=e,{caps:i,mode:a,scope:o}=this.resolveCaps(r.agentName,``,t),s=this.accumulators.get(r.taskId);if(!s)return;let c=i.filter(e=>e.field!==`context`);if(c.length===0)return;let l=s.toolCalls.get(t)??0,u=this.buildSnapshot(c,s,r.taskId,r.sessionId,r.agentName,o);for(let e of c){let i=this.resolveCapLimit(e,r),s=e.field===`toolCalls`?l:this.getSnapshotValue(u,e,o);if(s>=i){let o=e.message??`tool "${t}": ${e.field} limit is ${i}, current value is ${Math.round(s)}`;throw(e.mode??a??`warning`)===`warning`?(await this.emitBudgetEvent(`budget:warning`,r,e,s,i,o),_(t,n,o)):(await this.emitBudgetEvent(`budget:block`,r,e,s,i,o),g(`tool:${t}:${e.field}`,i,s,e.message))}}s.toolCalls.set(t,l+1)}enforceResponseSize(e){let{toolName:t,result:n}=e;if(typeof n!=`object`||!n)return;let{caps:r,mode:i}=this.resolveCaps(``,``,t),a=r.filter(e=>e.field===`responseSize`);if(a.length===0)return;let o=n,s=o.content;if(!Array.isArray(s))return;let c=0;for(let e of s)if(typeof e==`object`&&e){let t=e;t.type===`text`&&(c+=(t.text??``).length)}for(let t of a){let n=this.resolveCapLimit(t,e.executionContext);if(!(c<=n)){if((t.mode??i??`warning`)===`block`)o.content=[{type:`text`,text:t.message??`Response size (${c} chars) exceeds limit of ${n}. Use offset/limit or shell paging tools instead.`}],e.isError=!0;else{let e=n,r=[];for(let t of s){if(typeof t!=`object`||!t){r.push(t);continue}let n=t;if(n.type!==`text`){r.push(t);continue}let i=n.text??``;if(i.length<=e)r.push(t),e-=i.length;else{r.push({type:`text`,text:i.slice(0,e)});break}}r.push({type:`text`,text:`\n\n[truncated: response was ${c} chars, limited to ${t.maximum}. ${t.message??`Use offset/limit or shell paging tools for full content.`}]`}),o.content=r}break}}}},b=({db:e,config:t})=>{let n=h(t),r=n.defaults?.costPerInputToken??0;return new y(e,n,r,n.defaults?.costPerOutputToken??0,n.defaults?.costPerCacheReadToken??r,n.defaults?.costPerCacheWriteToken??r)};exports.configSchema=u,exports.createPlugin=b,exports.default=b,exports.pluginConfigSchema=l;
33
+ `)),s?.total??0}catch{return 0}}costFromTotals(e){return e.uncachedInputTokens*this.costPerInput+e.cacheReadTokens*this.costPerCacheRead+e.cacheCreationTokens*this.costPerCacheWrite+e.outputTokens*this.costPerOutput}buildSnapshot(e,t,n,i,a,o,s=0){let c={};c.inputTokens=t.inputTokens,c.outputTokens=t.outputTokens,c.calls=t.modelCalls,c.wallClock=Date.now()-t.startedAtMs,c.modelMs=t.totalModelMs,c.context=Math.max(t.peakContextTokens,s),c.errors=t.errors,c.consecutiveErrors=t.consecutiveErrors,c.cost=this.costFromTotals(t);let l=new Set,u=new Map;for(let t of e){let e=t.scope??o??`task`;if(e!==`task`&&l.add(e),t.window){let n=`${e}:${t.window}`;u.has(n)||u.set(n,{scope:e,windowMs:r(t.window)})}}for(let e of l){let t=this.queryScopeTotals(n,i,a,e);c[`${e}:inputTokens`]=t.inputTokens,c[`${e}:outputTokens`]=t.outputTokens,c[`${e}:calls`]=t.modelCalls,c[`${e}:context`]=Math.max(t.peakContextTokens,s),c[`${e}:cost`]=this.costFromTotals(t)}for(let[e,{scope:t,windowMs:r}]of u){let o=``;t===`session`?o=i??``:t===`agent`&&(o=a??``),c[e]=this.queryWindowTokens(t,o,r,n)}return c}getSnapshotValue(e,t,n){let r=t.scope??n??`task`,i;i=t.window||r===`task`?``:`${r}:`;let a;switch(t.field){case`inputTokens`:a=e[`${i}inputTokens`]??e.inputTokens;break;case`outputTokens`:a=e[`${i}outputTokens`]??e.outputTokens;break;case`context`:a=e[`${i}context`]??e.context;break;case`calls`:a=e[`${i}calls`]??e.calls;break;case`wallClock`:a=e.wallClock;break;case`modelMs`:a=e.modelMs;break;case`cost`:a=e[`${i}cost`]??e.cost;break;case`toolCalls`:a=0;break;case`errors`:a=e.errors;break;case`consecutiveErrors`:a=e.consecutiveErrors;break;default:a=0}return t.window&&(a+=e[`${r}:${t.window}`]??0),a}async emitBudgetEvent(e,t,n,r,i,a){this.hooks&&await this.hooks.emit(e,{executionContext:t,field:n.field,maximum:i,current:r,message:a??n.message??`${n.field} limit is ${i}, current value is ${Math.round(r)}`})}resolveCapLimit(e,t){if(e.maximum!==void 0)return e.maximum;let r=t.wireModel??(t.selection?.modelRef||void 0)??t.agentDefinition.provider?.model;return Math.floor((0,n.contextWindowFor)(r)*(e.contextWindowFraction??0))}async evaluateCap(e,t,n,r,i){let a=this.resolveCapLimit(e,n),o=this.getSnapshotValue(t,e,i);if(!(o<a)){if((e.mode??r??`warning`)===`warning`){await this.emitBudgetEvent(`budget:warning`,n,e,o,a);return}throw await this.emitBudgetEvent(`budget:block`,n,e,o,a),g(e.field,a,o,e.message)}}async enforcePreModel(e){let{taskId:t,sessionId:n,agentName:r}=e.executionContext,i=e.executionContext.agentDefinition?.provider?.type??`unknown`,o=this.accumulators.get(t);if(!o)return;let{caps:s,mode:c,scope:l}=this.resolveCaps(r,i),u=s.filter(e=>!a.has(e.field));if(u.length===0)return;let d=this.buildSnapshot(u,o,t,n,r,l,v(e.messages,e.tools));for(let t of u)await this.evaluateCap(t,d,e.executionContext,c,l)}async enforcePreTool(e){let{toolName:t,callId:n,executionContext:r}=e,{caps:i,mode:a,scope:o}=this.resolveCaps(r.agentName,``,t),s=this.accumulators.get(r.taskId);if(!s)return;let c=i.filter(e=>e.field!==`context`);if(c.length===0)return;let l=s.toolCalls.get(t)??0,u=this.buildSnapshot(c,s,r.taskId,r.sessionId,r.agentName,o);for(let e of c){let i=this.resolveCapLimit(e,r),s=e.field===`toolCalls`?l:this.getSnapshotValue(u,e,o);if(s>=i){let o=e.message??`tool "${t}": ${e.field} limit is ${i}, current value is ${Math.round(s)}`;throw(e.mode??a??`warning`)===`warning`?(await this.emitBudgetEvent(`budget:warning`,r,e,s,i,o),_(t,n,o)):(await this.emitBudgetEvent(`budget:block`,r,e,s,i,o),g(`tool:${t}:${e.field}`,i,s,e.message))}}s.toolCalls.set(t,l+1)}enforceResponseSize(e){let{toolName:t,result:n}=e;if(typeof n!=`object`||!n)return;let{caps:r,mode:i}=this.resolveCaps(``,``,t),a=r.filter(e=>e.field===`responseSize`);if(a.length===0)return;let o=n,s=o.content;if(!Array.isArray(s))return;let c=0;for(let e of s)if(typeof e==`object`&&e){let t=e;t.type===`text`&&(c+=(t.text??``).length)}for(let t of a){let n=this.resolveCapLimit(t,e.executionContext);if(!(c<=n)){if((t.mode??i??`warning`)===`block`)o.content=[{type:`text`,text:t.message??`Response size (${c} chars) exceeds limit of ${n}. Use offset/limit or shell paging tools instead.`}],e.isError=!0;else{let e=n,r=[];for(let t of s){if(typeof t!=`object`||!t){r.push(t);continue}let n=t;if(n.type!==`text`){r.push(t);continue}let i=n.text??``;if(i.length<=e)r.push(t),e-=i.length;else{r.push({type:`text`,text:i.slice(0,e)});break}}r.push({type:`text`,text:`\n\n[truncated: response was ${c} chars, limited to ${t.maximum}. ${t.message??`Use offset/limit or shell paging tools for full content.`}]`}),o.content=r}break}}}},b=({db:e,config:t})=>{let n=h(t),r=n.defaults?.costPerInputToken??0;return new y(e,n,r,n.defaults?.costPerOutputToken??0,n.defaults?.costPerCacheReadToken??r,n.defaults?.costPerCacheWriteToken??r)};exports.configSchema=u,exports.createPlugin=b,exports.default=b,exports.pluginConfigSchema=l;
package/index.js CHANGED
@@ -476,8 +476,8 @@ var y = class {
476
476
  }
477
477
  resolveCapLimit(e, t) {
478
478
  if (e.maximum !== void 0) return e.maximum;
479
- let r = t.agentDefinition.provider;
480
- return Math.floor(n(r.model) * (e.contextWindowFraction ?? 0));
479
+ let r = t.wireModel ?? (t.selection?.modelRef || void 0) ?? t.agentDefinition.provider?.model;
480
+ return Math.floor(n(r) * (e.contextWindowFraction ?? 0));
481
481
  }
482
482
  async evaluateCap(e, t, n, r, i) {
483
483
  let a = this.resolveCapLimit(e, n), o = this.getSnapshotValue(t, e, i);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhd/agent-plugin-budget",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Budget enforcement plugin for @adhd/agent-mcp — caps token spend, cost, wall-clock time, and model calls per task/session/agent",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -17,7 +17,7 @@
17
17
  }
18
18
  },
19
19
  "peerDependencies": {
20
- "@adhd/agent-base-types": "^2.3.4"
20
+ "@adhd/agent-base-types": "^2.3.5"
21
21
  },
22
22
  "dependencies": {
23
23
  "drizzle-orm": "0.45.2",