@lensmcp/core 1.25.36 → 1.25.38

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.
@@ -100,6 +100,8 @@ export interface AnswerCut {
100
100
  repeatedStrings: number;
101
101
  /** Cutting lists was not enough: large values elsewhere in the document were shrunk too. */
102
102
  clamped?: true;
103
+ /** The values it holds that were sent whole, whatever the budget ({@link AnswerFitOptions.whole}). */
104
+ whole?: string[];
103
105
  note: string;
104
106
  }
105
107
  export interface AnswerFitOptions {
@@ -107,6 +109,15 @@ export interface AnswerFitOptions {
107
109
  more?: string;
108
110
  /** Per top-level list: the end that holds its newest items, for a ring whose items carry no time. */
109
111
  newest?: Readonly<Record<string, ListEnd>>;
112
+ /**
113
+ * Values never cut, by their path of keys from the document's root (`result.role.rules`): text an agent acts on only
114
+ * as a whole — its own guide, whose head it would otherwise take for all of it (issues/closed/foodguard-116). Their
115
+ * bytes are spent first, the rest of the document is fitted into what they leave (at least
116
+ * {@link MIN_ANSWER_BUDGET_BYTES}), and they go back as they were — so values that alone outgrow the budget take the
117
+ * answer over it: bound them where they are made. A path that is not there, or that passes through a list, holds
118
+ * nothing.
119
+ */
120
+ whole?: readonly string[];
110
121
  }
111
122
  /**
112
123
  * Fit a whole answer into the budget.
@@ -114,7 +125,8 @@ export interface AnswerFitOptions {
114
125
  * An answer that fits is returned as it is, the SAME value. Over the budget, its top-level lists
115
126
  * are cut ({@link fitList}; small lists whole, large ones sharing what the rest of the document
116
127
  * leaves), and the result carries {@link ANSWER_CUT_KEY} as its first key. A document that is
117
- * large somewhere other than a list is shrunk by the event clamp, which keeps its shape.
128
+ * large somewhere other than a list is shrunk by the event clamp, which keeps its shape. What
129
+ * `whole` names is set aside before any of it, and goes back as it was.
118
130
  */
119
131
  export declare function fitAnswer<T>(value: T, budgetBytes: number, opts?: AnswerFitOptions): {
120
132
  value: T;
@@ -1,2 +1,2 @@
1
- "use strict";var W=Object.defineProperty;var d=(e,t)=>W(e,"name",{value:t,configurable:!0});var Y=Object.defineProperty,f=d((e,t)=>Y(e,"name",{value:t,configurable:!0}),"a");import{boundedClone as G}from"@lensmcp/protocol-types";export const DEFAULT_ANSWER_BUDGET_BYTES=24*1024,ANSWER_BUDGET_ENV="LENSMCP_ANSWER_BUDGET_BYTES",MIN_ANSWER_BUDGET_BYTES=2*1024,ANSWER_CUT_KEY="cutToFit",FITTED_STRING_CHARS=1500,REPEATED_STRING_MIN_CHARS=200;const j=900,L=32,z=new Set(["timestamp","startedAt","startTime","at","emittedAt","lastSeenAt","endedAt","endTime","t","ts","time"]),V=f(e=>typeof e=="number"&&e>=1e11&&e<=1e14,"isEpochMs"),H=.95;export function answerBudgetBytes(e=process.env){const t=e[ANSWER_BUDGET_ENV];if(t===void 0||t.trim()==="")return DEFAULT_ANSWER_BUDGET_BYTES;const r=Number(t);return!Number.isFinite(r)||r<0?DEFAULT_ANSWER_BUDGET_BYTES:r===0?Number.POSITIVE_INFINITY:Math.max(MIN_ANSWER_BUDGET_BYTES,Math.floor(r))}d(answerBudgetBytes,"answerBudgetBytes"),f(answerBudgetBytes,"answerBudgetBytes");export function utf8Bytes(e){let t=0;for(let r=0;r<e.length;r++){const i=e.charCodeAt(r);i<128?t+=1:i<2048?t+=2:i>=55296&&i<=56319&&r+1<e.length&&(e.charCodeAt(r+1)&64512)===56320?(t+=4,r+=1):t+=3}return t}d(utf8Bytes,"utf8Bytes"),f(utf8Bytes,"utf8Bytes");export function jsonBytes(e){let t;try{t=JSON.stringify(e)}catch{return Number.POSITIVE_INFINITY}return t===void 0?0:utf8Bytes(t)}d(jsonBytes,"jsonBytes"),f(jsonBytes,"jsonBytes");const O=f(e=>typeof e=="object"&&e!==null&&!Array.isArray(e),"isRecord");function R(e){if(!O(e))return!1;const t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}d(R,"D"),f(R,"isPlain");export function newestEndOf(e){const t=e[0];if(e.length<2||!O(t))return;let r;for(const i of Object.keys(t)){if(typeof t[i]!="number"||!(z.has(i)||V(t[i])))continue;let s=0,h=0,l=!0,g=t[i];for(let E=1;E<e.length;E++){const w=e[E],p=O(w)?w[i]:void 0;if(typeof p!="number"){l=!1;break}p>g?s+=1:p<g&&(h+=1),g=p}const y=s+h;if(!l||y===0)continue;const x=Math.max(s,h)/y;x>=H&&(!r||x>r.agreement)&&(r={end:s>=h?"end":"start",agreement:x})}return r?.end}d(newestEndOf,"newestEndOf"),f(newestEndOf,"newestEndOf");export function newestFirst(e){return newestEndOf(e)==="end"?[...e].reverse():e.slice()}d(newestFirst,"newestFirst"),f(newestFirst,"newestFirst");export function shortenText(e,t){if(e.length<=t)return e;let r=Math.max(0,t);const i=e.lastIndexOf(`
2
- `,r);return i>=r/2&&(r=i),r>0&&(e.charCodeAt(r-1)&64512)===55296&&(r-=1),`${e.slice(0,r)}\u2026[lensmcp truncated: ${e.length-r} more chars]`}d(shortenText,"shortenText"),f(shortenText,"shortenText");export function fitList(e,t,r={}){const i=e.length,s=e.reduce((o,u)=>o+jsonBytes(u)+1,2);if(s<=t)return{items:e.slice(),total:i,returned:i,kept:"all",shortenedStrings:0,repeatedStrings:0,bytes:s};const h=r.newest??newestEndOf(e),l=h??r.keep??"start",g=r.name??"items",y=r.maxStringChars??FITTED_STRING_CHARS,x=l==="start"?e:[...e].reverse(),E=String(Math.max(0,i-1)).length,w=new Map,p=[],c=[];let S=2,b=0,$=0;for(let o=0;o<x.length;o++){const u=new Map,_=[];let M=0;const v=f((a,N,I,P,U)=>{if(typeof a=="string"){if(a.length>=REPEATED_STRING_MIN_CHARS){const m=w.get(a)??u.get(a);if(m)return _.push({holder:P,key:U,owner:m.owner,path:m.path}),`\u2026[lensmcp: same text as ${g}[${"9".repeat(E)}]${m.path}]`;u.set(a,{owner:o,path:N})}return a.length>y?(M+=1,shortenText(a,y)):a}if(I>=L)return a;if(Array.isArray(a)){const m=[];return a.forEach((T,D)=>m.push(v(T,`${N}[${D}]`,I+1,m,D))),m}if(R(a)){const m={};for(const T of Object.keys(a))m[T]=v(a[T],`${N}.${T}`,I+1,m,T);return m}return a},"slim"),F=[];let k=v(x[o],"",0,F,0),B=jsonBytes(k)+1;if(c.length===0&&S+B>t)k=C(x[o],Math.max(0,t-S-1)),B=jsonBytes(k)+1,_.length=0,u.clear(),M=1;else if(S+B>t)break;c.push(k),S+=B,b+=M,$+=_.length;for(const[a,N]of u)w.set(a,N);for(const a of _)p.push(a.holder===F?{...a,holder:c,key:c.length-1}:a)}const A=c.length,n=f(o=>l==="start"?o:A-1-o,"indexOf");if(l==="end"){for(const o of p)o.holder===c&&(o.key=n(o.key));c.reverse()}for(const o of p)o.holder[o.key]=`\u2026[lensmcp: same text as ${g}[${n(o.owner)}]${o.path}]`;return{items:c,total:i,returned:A,kept:A===i?"all":h?"newest":l==="start"?"first":"last",shortenedStrings:b,repeatedStrings:$,bytes:jsonBytes(c)}}d(fitList,"fitList"),f(fitList,"fitList");function C(e,t){let r=Math.max(256,t),i=e;for(let s=0;s<6;s++){if(i=G(e,{rawByteLimit:r,maxStringBytes:Math.min(2e3,Math.max(128,Math.floor(r/4))),maxArrayItems:64}).value,jsonBytes(i)<=t)return i;r=Math.floor(r*.6)}return i}d(C,"Y"),f(C,"clampTo");export function shareBudget(e,t){const r=e.map((h,l)=>({size:h,index:l})).sort((h,l)=>h.size-l.size),i=new Array(e.length).fill(0);let s=Math.max(0,t);return r.forEach(({size:h,index:l},g)=>{const y=Math.floor(s/(r.length-g));i[l]=Math.min(h,y),s-=i[l]}),i}d(shareBudget,"shareBudget"),f(shareBudget,"shareBudget");export function fitAnswer(e,t,r={}){if(!Number.isFinite(t)||e===null||typeof e!="object")return{value:e};const i=jsonBytes(e);if(i<=t)return{value:e};const s={limitBytes:t,originalBytes:i,lists:[],shortenedStrings:0,repeatedStrings:0,note:""},h=f(()=>{const n=s.lists.map(o=>`\`${o.list}\` shows the ${o.kept} ${o.returned} of ${o.total}`);s.note=`Cut to fit one tool result (${t} bytes; the whole answer is ${Number.isFinite(i)?i:"larger"}) \u2014 it is still one valid JSON document.`+(n.length?` ${n.join("; ")}; any other count in this document describes the whole list.`:"")+(s.shortenedStrings?` ${s.shortenedStrings} long text${s.shortenedStrings===1?" was":"s were"} cut to the head.`:"")+(s.repeatedStrings?` ${s.repeatedStrings} repeated text${s.repeatedStrings===1?" is":"s are"} stated once and referenced.`:"")+(s.clamped?" Large values outside the lists were shrunk too (see `__lensmcpTruncation`).":"")+(r.more?` ${r.more}`:"")+` Whole answer: start the lens with ${ANSWER_BUDGET_ENV}=0.`},"describe");if(Array.isArray(e)){const n=fitList(e,t);return n.kept!=="all"&&s.lists.push({list:"items",total:n.total,returned:n.returned,kept:n.kept}),s.shortenedStrings=n.shortenedStrings,s.repeatedStrings=n.repeatedStrings,h(),{value:n.items,cut:s}}if(!R(e))return{value:e};const l=Object.keys(e),g=l.filter(n=>Array.isArray(e[n])&&e[n].length>0),y=g.map(n=>jsonBytes(e[n])),x=i-y.reduce((n,o)=>n+o,0),E=shareBudget(y,t-x-j),w={};g.forEach((n,o)=>{if(y[o]<=E[o])return;const u=fitList(e[n],E[o],{name:n,...r.newest?.[n]?{newest:r.newest[n]}:{}});w[n]=u.items,u.kept!=="all"&&s.lists.push({list:n,total:u.total,returned:u.returned,kept:u.kept}),s.shortenedStrings+=u.shortenedStrings,s.repeatedStrings+=u.repeatedStrings});const p=f(n=>(h(),{[ANSWER_CUT_KEY]:s,...n}),"assemble"),c={};for(const n of l)c[n]=n in w?w[n]:e[n];let S=p(c);if(jsonBytes(S)<=t)return{value:S,cut:s};s.clamped=!0;let b=Math.max(512,t-j);for(let n=0;n<6;n++){if(S=p(C(c,b)),jsonBytes(S)<=t)return{value:S,cut:s};b=Math.floor(b*.6)}const $={};let A=j;for(const n of l){const o=e[n];if(o!==null&&typeof o=="object")continue;const u=jsonBytes(o)+utf8Bytes(n)+4;if(A+u>t)break;$[n]=o,A+=u}return{value:p($),cut:s}}d(fitAnswer,"fitAnswer"),f(fitAnswer,"fitAnswer");
1
+ "use strict";var U=Object.defineProperty;var y=(e,r)=>U(e,"name",{value:r,configurable:!0});var Y=Object.defineProperty,p=y((e,r)=>Y(e,"name",{value:r,configurable:!0}),"u");import{boundedClone as z}from"@lensmcp/protocol-types";export const DEFAULT_ANSWER_BUDGET_BYTES=24*1024,ANSWER_BUDGET_ENV="LENSMCP_ANSWER_BUDGET_BYTES",MIN_ANSWER_BUDGET_BYTES=2*1024,ANSWER_CUT_KEY="cutToFit",FITTED_STRING_CHARS=1500,REPEATED_STRING_MIN_CHARS=200;const R=900,V=32,H=new Set(["timestamp","startedAt","startTime","at","emittedAt","lastSeenAt","endedAt","endTime","t","ts","time"]),J=p(e=>typeof e=="number"&&e>=1e11&&e<=1e14,"isEpochMs"),K=.95;export function answerBudgetBytes(e=process.env){const r=e[ANSWER_BUDGET_ENV];if(r===void 0||r.trim()==="")return DEFAULT_ANSWER_BUDGET_BYTES;const t=Number(r);return!Number.isFinite(t)||t<0?DEFAULT_ANSWER_BUDGET_BYTES:t===0?Number.POSITIVE_INFINITY:Math.max(MIN_ANSWER_BUDGET_BYTES,Math.floor(t))}y(answerBudgetBytes,"answerBudgetBytes"),p(answerBudgetBytes,"answerBudgetBytes");export function utf8Bytes(e){let r=0;for(let t=0;t<e.length;t++){const s=e.charCodeAt(t);s<128?r+=1:s<2048?r+=2:s>=55296&&s<=56319&&t+1<e.length&&(e.charCodeAt(t+1)&64512)===56320?(r+=4,t+=1):r+=3}return r}y(utf8Bytes,"utf8Bytes"),p(utf8Bytes,"utf8Bytes");export function jsonBytes(e){let r;try{r=JSON.stringify(e)}catch{return Number.POSITIVE_INFINITY}return r===void 0?0:utf8Bytes(r)}y(jsonBytes,"jsonBytes"),p(jsonBytes,"jsonBytes");const C=p(e=>typeof e=="object"&&e!==null&&!Array.isArray(e),"isRecord");function _(e){if(!C(e))return!1;const r=Object.getPrototypeOf(e);return r===Object.prototype||r===null}y(_,"O"),p(_,"isPlain");export function newestEndOf(e){const r=e[0];if(e.length<2||!C(r))return;let t;for(const s of Object.keys(r)){if(typeof r[s]!="number"||!(H.has(s)||J(r[s])))continue;let i=0,a=0,u=!0,c=r[s];for(let w=1;w<e.length;w++){const S=e[w],m=C(S)?S[s]:void 0;if(typeof m!="number"){u=!1;break}m>c?i+=1:m<c&&(a+=1),c=m}const o=i+a;if(!u||o===0)continue;const f=Math.max(i,a)/o;f>=K&&(!t||f>t.agreement)&&(t={end:i>=a?"end":"start",agreement:f})}return t?.end}y(newestEndOf,"newestEndOf"),p(newestEndOf,"newestEndOf");export function newestFirst(e){return newestEndOf(e)==="end"?[...e].reverse():e.slice()}y(newestFirst,"newestFirst"),p(newestFirst,"newestFirst");export function shortenText(e,r){if(e.length<=r)return e;let t=Math.max(0,r);const s=e.lastIndexOf(`
2
+ `,t);return s>=t/2&&(t=s),t>0&&(e.charCodeAt(t-1)&64512)===55296&&(t-=1),`${e.slice(0,t)}\u2026[lensmcp truncated: ${e.length-t} more chars]`}y(shortenText,"shortenText"),p(shortenText,"shortenText");export function fitList(e,r,t={}){const s=e.length,i=e.reduce((l,k)=>l+jsonBytes(k)+1,2);if(i<=r)return{items:e.slice(),total:s,returned:s,kept:"all",shortenedStrings:0,repeatedStrings:0,bytes:i};const a=t.newest??newestEndOf(e),u=a??t.keep??"start",c=t.name??"items",o=t.maxStringChars??FITTED_STRING_CHARS,f=u==="start"?e:[...e].reverse(),w=String(Math.max(0,s-1)).length,S=new Map,m=[],b=[];let $=2,B=0,T=0;for(let l=0;l<f.length;l++){const k=new Map,E=[];let M=0;const n=p((h,I,j,G,L)=>{if(typeof h=="string"){if(h.length>=REPEATED_STRING_MIN_CHARS){const x=S.get(h)??k.get(h);if(x)return E.push({holder:G,key:L,owner:x.owner,path:x.path}),`\u2026[lensmcp: same text as ${c}[${"9".repeat(w)}]${x.path}]`;k.set(h,{owner:l,path:I})}return h.length>o?(M+=1,shortenText(h,o)):h}if(j>=V)return h;if(Array.isArray(h)){const x=[];return h.forEach((N,W)=>x.push(n(N,`${I}[${W}]`,j+1,x,W))),x}if(_(h)){const x={};for(const N of Object.keys(h))x[N]=n(h[N],`${I}.${N}`,j+1,x,N);return x}return h},"slim"),d=[];let g=n(f[l],"",0,d,0),O=jsonBytes(g)+1;if(b.length===0&&$+O>r)g=F(f[l],Math.max(0,r-$-1)),O=jsonBytes(g)+1,E.length=0,k.clear(),M=1;else if($+O>r)break;b.push(g),$+=O,B+=M,T+=E.length;for(const[h,I]of k)S.set(h,I);for(const h of E)m.push(h.holder===d?{...h,holder:b,key:b.length-1}:h)}const A=b.length,v=p(l=>u==="start"?l:A-1-l,"indexOf");if(u==="end"){for(const l of m)l.holder===b&&(l.key=v(l.key));b.reverse()}for(const l of m)l.holder[l.key]=`\u2026[lensmcp: same text as ${c}[${v(l.owner)}]${l.path}]`;return{items:b,total:s,returned:A,kept:A===s?"all":a?"newest":u==="start"?"first":"last",shortenedStrings:B,repeatedStrings:T,bytes:jsonBytes(b)}}y(fitList,"fitList"),p(fitList,"fitList");function F(e,r){let t=Math.max(256,r),s=e;for(let i=0;i<6;i++){if(s=z(e,{rawByteLimit:t,maxStringBytes:Math.min(2e3,Math.max(128,Math.floor(t/4))),maxArrayItems:64}).value,jsonBytes(s)<=r)return s;t=Math.floor(t*.6)}return s}y(F,"W"),p(F,"clampTo");export function shareBudget(e,r){const t=e.map((a,u)=>({size:a,index:u})).sort((a,u)=>a.size-u.size),s=new Array(e.length).fill(0);let i=Math.max(0,r);return t.forEach(({size:a,index:u},c)=>{const o=Math.floor(i/(t.length-c));s[u]=Math.min(a,o),i-=s[u]}),s}y(shareBudget,"shareBudget"),p(shareBudget,"shareBudget");function P(e,r){const t={...e},s=new WeakSet([t]),i=[];let a=0;for(const u of r){const c=u.split("."),o=c[c.length-1];let f=t;for(const w of c.slice(0,-1)){const S=f[w];if(!_(S)){f=void 0;break}const m=s.has(S)?S:{...S};s.add(m),f[w]=m,f=m}!f||f[o]===void 0||f[o]===null||(i.push({keys:c,value:f[o]}),a+=jsonBytes(f[o]),f[o]=null)}return i.length?{body:t,parts:i,bytes:a}:void 0}y(P,"K"),p(P,"holdWhole");function D(e,r){for(const{keys:t,value:s}of r){let i=e;for(const a of t.slice(0,-1)){const u=i[a],c=_(u)?u:{};i[a]=c,i=c}i[t[t.length-1]]=s}return e}y(D,"z"),p(D,"putBack");export function fitAnswer(e,r,t={}){if(!Number.isFinite(r)||e===null||typeof e!="object")return{value:e};const s=jsonBytes(e);if(s<=r)return{value:e};const i=t.whole?.length&&_(e)?P(e,t.whole):void 0,a=i?i.body:e,u=i?jsonBytes(a):s,c=i?Math.max(MIN_ANSWER_BUDGET_BYTES,r-i.bytes):r;if(u<=c)return{value:e};const o={limitBytes:r,originalBytes:s,lists:[],shortenedStrings:0,repeatedStrings:0,...i?{whole:i.parts.map(n=>n.keys.join("."))}:{},note:""},f=p(()=>{const n=o.lists.map(d=>`\`${d.list}\` shows the ${d.kept} ${d.returned} of ${d.total}`);o.note=`Cut to fit one tool result (${r} bytes; the whole answer is ${Number.isFinite(s)?s:"larger"}) \u2014 it is still one valid JSON document.`+(n.length?` ${n.join("; ")}; any other count in this document describes the whole list.`:"")+(o.shortenedStrings?` ${o.shortenedStrings} long text${o.shortenedStrings===1?" was":"s were"} cut to the head.`:"")+(o.repeatedStrings?` ${o.repeatedStrings} repeated text${o.repeatedStrings===1?" is":"s are"} stated once and referenced.`:"")+(o.clamped?" Large values outside the lists were shrunk too (see `__lensmcpTruncation`).":"")+(o.whole?` Never cut: ${o.whole.map(d=>`\`${d}\``).join(", ")} \u2014 sent whole.`:"")+(t.more?` ${t.more}`:"")+` Whole answer: start the lens with ${ANSWER_BUDGET_ENV}=0.`},"describe"),w=p(n=>({value:i?D(n,i.parts):n,cut:o}),"done");if(Array.isArray(a)){const n=fitList(a,c);return n.kept!=="all"&&o.lists.push({list:"items",total:n.total,returned:n.returned,kept:n.kept}),o.shortenedStrings=n.shortenedStrings,o.repeatedStrings=n.repeatedStrings,f(),w(n.items)}if(!_(a))return{value:e};const S=Object.keys(a),m=S.filter(n=>Array.isArray(a[n])&&a[n].length>0),b=m.map(n=>jsonBytes(a[n])),$=u-b.reduce((n,d)=>n+d,0),B=shareBudget(b,c-$-R),T={};m.forEach((n,d)=>{if(b[d]<=B[d])return;const g=fitList(a[n],B[d],{name:n,...t.newest?.[n]?{newest:t.newest[n]}:{}});T[n]=g.items,g.kept!=="all"&&o.lists.push({list:n,total:g.total,returned:g.returned,kept:g.kept}),o.shortenedStrings+=g.shortenedStrings,o.repeatedStrings+=g.repeatedStrings});const A=p(n=>(f(),{[ANSWER_CUT_KEY]:o,...n}),"assemble"),v={};for(const n of S)v[n]=n in T?T[n]:a[n];let l=A(v);if(jsonBytes(l)<=c)return w(l);o.clamped=!0;let k=Math.max(512,c-R);for(let n=0;n<6;n++){if(l=A(F(v,k)),jsonBytes(l)<=c)return w(l);k=Math.floor(k*.6)}const E={};let M=R;for(const n of S){const d=a[n];if(d!==null&&typeof d=="object")continue;const g=jsonBytes(d)+utf8Bytes(n)+4;if(M+g>c)break;E[n]=d,M+=g}return w(A(E))}y(fitAnswer,"fitAnswer"),p(fitAnswer,"fitAnswer");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/core",
3
- "version": "1.25.36",
3
+ "version": "1.25.38",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -84,7 +84,7 @@
84
84
  }
85
85
  },
86
86
  "dependencies": {
87
- "@lensmcp/protocol-types": "1.25.36",
87
+ "@lensmcp/protocol-types": "1.25.38",
88
88
  "tslib": "^2.3.0"
89
89
  },
90
90
  "license": "Apache-2.0",