@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.
- package/lib/answer-budget.d.ts +13 -1
- package/lib/answer-budget.js +2 -2
- package/package.json +2 -2
package/lib/answer-budget.d.ts
CHANGED
|
@@ -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;
|
package/lib/answer-budget.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`,
|
|
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.
|
|
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.
|
|
87
|
+
"@lensmcp/protocol-types": "1.25.38",
|
|
88
88
|
"tslib": "^2.3.0"
|
|
89
89
|
},
|
|
90
90
|
"license": "Apache-2.0",
|