@lensmcp/node-instrumentation 1.24.5 → 1.25.0

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.
@@ -0,0 +1,31 @@
1
+ import type { HttpExchange, HttpExchangeBody } from '@lensmcp/core/http-exchange';
2
+ import { type HttpCaptureMode } from './http-store';
3
+ type FetchInput = Parameters<typeof fetch>[0];
4
+ type FetchInit = Parameters<typeof fetch>[1];
5
+ export interface RequestSide {
6
+ headers: Array<[string, string]>;
7
+ contentType: string;
8
+ /** The body as bytes, when it could be read without consuming it. */
9
+ bytes?: Uint8Array;
10
+ pending?: Promise<Uint8Array | undefined>;
11
+ size?: number;
12
+ omitted?: HttpExchangeBody['omitted'];
13
+ }
14
+ /** The request's headers and body, taken BEFORE the call. A stream or FormData is not read — it would be consumed. */
15
+ export declare function takeRequest(input: FetchInput, init: FetchInit, bodies: boolean): RequestSide;
16
+ export interface ResponseRead {
17
+ body?: HttpExchangeBody;
18
+ stream?: NonNullable<HttpExchange['response']['stream']>;
19
+ endedAt: number;
20
+ }
21
+ /**
22
+ * Read the response from its clone. MUST be called synchronously after `res.clone()` (see above). An AI provider's
23
+ * stream keeps each event's name, time and size, never its data; its body is not kept at all.
24
+ */
25
+ export declare function readResponse(res: Response, clone: Response, startedAt: number, opts: {
26
+ ai: boolean;
27
+ paths: string[];
28
+ }): Promise<ResponseRead>;
29
+ /** The request side as kept: headers redacted, the query, the body (or why there is none). */
30
+ export declare function requestPart(side: RequestSide, search: string, mode: HttpCaptureMode, ai: boolean, paths: string[], redacted: string[]): Promise<HttpExchange['request']>;
31
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";var q=Object.defineProperty;var g=(e,r)=>q(e,"name",{value:r,configurable:!0});var k=Object.defineProperty,l=g((e,r)=>k(e,"name",{value:r,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.takeRequest=takeRequest,exports.readResponse=readResponse,exports.requestPart=requestPart;const node_crypto_1=require("node:crypto"),ai_store_1=require("./ai-store"),http_store_1=require("./http-store"),MAX_EVENTS=2e3,MAX_EVENT_DATA=4*1024,MAX_STREAM_KEPT=256*1024,MAX_STREAM_MS=300*1e3,MAX_HASH_BYTES=1024*1024,pairs=l(e=>{try{if(!e)return[];if(typeof e.forEach=="function"&&typeof e.get=="function"){const r=[];return e.forEach((o,n)=>r.push([n,o])),r}return Array.isArray(e)?e.map(([r,o])=>[String(r),String(o)]):Object.entries(e).map(([r,o])=>[r,Array.isArray(o)?o.join(", "):String(o)])}catch{return[]}},"pairs");function takeRequest(e,r,o){const n=typeof e=="object"&&e!==null&&!(e instanceof URL)?e:void 0,i=[...pairs(n?.headers),...pairs(r?.headers)],c=i.find(([s])=>s.toLowerCase()==="content-type")?.[1]??"",a={headers:i,contentType:c};if(!o)return a;const t=r?.body,d=l(s=>new TextEncoder().encode(s),"enc");if(typeof t=="string")return{...a,bytes:d(t),size:Buffer.byteLength(t)};if(t instanceof URLSearchParams)return{...a,contentType:c||"application/x-www-form-urlencoded",bytes:d(t.toString())};if(t instanceof ArrayBuffer)return{...a,bytes:new Uint8Array(t),size:t.byteLength};if(ArrayBuffer.isView(t))return{...a,bytes:new Uint8Array(t.buffer,t.byteOffset,t.byteLength),size:t.byteLength};if(typeof Blob<"u"&&t instanceof Blob)return{...a,size:t.size,...t.size<=http_store_1.MAX_KEPT_BYTES*32?{pending:t.arrayBuffer().then(s=>new Uint8Array(s)).catch(()=>{})}:{}};if(typeof FormData<"u"&&t instanceof FormData)return{...a,omitted:"form-data"};if(t!=null)return{...a,omitted:"stream"};if(n&&!n.bodyUsed&&n.body)try{return{...a,pending:n.clone().arrayBuffer().then(s=>new Uint8Array(s)).catch(()=>{})}}catch{return a}return a}g(takeRequest,"takeRequest"),l(takeRequest,"takeRequest");function sseEvent(e){let r="message";const o=[];for(const n of e.split(/\r?\n/)){if(!n||n.startsWith(":"))continue;const i=n.indexOf(":"),c=i<0?n:n.slice(0,i),a=i<0?"":n.slice(i+1).replace(/^ /,"");c==="event"?r=a||"message":c==="data"&&o.push(a)}return o.length||r!=="message"?{event:r,data:o.join(`
2
+ `)}:void 0}g(sseEvent,"sseEvent"),l(sseEvent,"sseEvent");function readResponse(e,r,o,n){const i=(()=>{try{return e.headers.get("content-type")??""}catch{return""}})(),c=Number((()=>{try{return e.headers.get("content-length")??""}catch{return""}})()),a=r.body;if(!a)return Promise.resolve({endedAt:Date.now(),...Number.isFinite(c)&&c===0?{}:{}});const t=a.getReader(),d=/text\/event-stream/i.test(i);return(async()=>{let s=0;const m=[];let f=0;const E=(0,node_crypto_1.createHash)("sha256");let v=!/json|^text\/|xml|form/i.test(i);const T=[];let L=0,x=0,_,A=!1,y="";const z=new TextDecoder,B=l(p=>{const u=sseEvent(p);if(!u)return;L++;const h=Date.now()-o;if(_??=h,T.length>=MAX_EVENTS||x>=MAX_STREAM_KEPT){A=!0;return}const b=n.ai?void 0:(0,ai_store_1.redactAiText)(u.data).slice(0,MAX_EVENT_DATA);b&&(x+=b.length),T.push({atMs:h,event:u.event,bytes:Buffer.byteLength(p),...b!==void 0?{data:b}:{}})},"take");try{for(;;){const{done:p,value:u}=await t.read();if(p)break;if(s+=u.byteLength,d){y+=z.decode(u,{stream:!0});let h;for(;(h=y.search(/\r?\n\r?\n/))>=0;){const b=/\r?\n\r?\n/.exec(y.slice(h));B(y.slice(0,h)),y=y.slice(h+b[0].length)}}else n.ai||(f<http_store_1.MAX_KEPT_BYTES*32&&(m.push(u),f+=u.byteLength),v&&(s<=MAX_HASH_BYTES?E.update(u):v=!1));if(Date.now()-o>MAX_STREAM_MS){A=!0,await t.cancel().catch(()=>{});break}}d&&y.trim()&&B(y)}catch{A=!0}const w=Date.now();if(d)return{endedAt:w,stream:{protocol:"sse",..._!==void 0?{firstEventMs:_}:{},events:T,bytes:s,total:L,...A?{cut:!0}:{}}};if(n.ai)return{endedAt:w,body:{contentType:i.split(";")[0].trim(),size:s,kept:0,truncated:!1,omitted:"ai"}};const M=new Uint8Array(f);let S=0;for(const p of m)M.set(p,S),S+=p.byteLength;const R=v&&s<=MAX_HASH_BYTES?E.digest("hex"):void 0;return{endedAt:w,body:(0,http_store_1.keepBody)(i,M,s,n.paths,R)}})()}g(readResponse,"readResponse"),l(readResponse,"readResponse");async function requestPart(e,r,o,n,i,c){const{headers:a,redacted:t}=(0,http_store_1.redactHeaders)(e.headers);c.push(...t);const d=o==="bodies"&&r.length>1?(0,http_store_1.redactQuery)(r):[];let s;if(o==="bodies"){const m=e.contentType.split(";")[0].trim().toLowerCase();if(n)s={contentType:m,size:e.size??e.bytes?.byteLength??0,kept:0,truncated:!1,omitted:"ai"};else if(e.omitted)s={contentType:m,size:e.size??0,kept:0,truncated:!1,omitted:e.omitted};else{const f=e.bytes??await e.pending;f&&f.byteLength&&(s=(0,http_store_1.keepBody)(e.contentType,f,e.size??f.byteLength,i))}}return{headers:a,...d.length?{query:d}:{},...s?{body:s}:{}}}g(requestPart,"requestPart"),l(requestPart,"requestPart");
@@ -0,0 +1,23 @@
1
+ import { type HttpExchange, type HttpExchangeBody } from '@lensmcp/core/http-exchange';
2
+ export type HttpCaptureMode = 'off' | 'headers' | 'bodies';
3
+ /** `LENSMCP_HTTP_CAPTURE`: `off`, `headers`, or `bodies` — the default (hub decision 12). */
4
+ export declare function httpCaptureMode(env?: Record<string, string | undefined>): HttpCaptureMode;
5
+ /** What a removed value reads as. */
6
+ export declare const REMOVED = "\u2022\u2022\u2022\u2022 redacted";
7
+ /** A body keeps its first 64 KB as text. */
8
+ export declare const MAX_KEPT_BYTES: number;
9
+ export declare const newExchangeId: () => string;
10
+ /** Headers as kept: credential values removed (a bearer JWT shown as its claims), the names that were. */
11
+ export declare function redactHeaders(entries: ReadonlyArray<readonly [string, string]>): {
12
+ headers: Array<[string, string]>;
13
+ redacted: string[];
14
+ };
15
+ /** The query string as pairs: a credential-named key's value removed, every value scrubbed of secret shapes. */
16
+ export declare function redactQuery(search: string): Array<[string, string]>;
17
+ /**
18
+ * A body as kept: its text (the first 64 KB), redacted by what it is — JSON and form fields by name, any text by the
19
+ * secret shapes — or, binary, its size (and digest). `bytes` is what was read; `size` every byte it had.
20
+ */
21
+ export declare function keepBody(contentType: string, bytes: Uint8Array, size: number, paths: string[], sha256?: string): HttpExchangeBody;
22
+ /** Persist one exchange (temp name, then rename). Best-effort: false on any failure — the call goes on regardless. */
23
+ export declare function writeHttpExchange(x: HttpExchange): boolean;
@@ -0,0 +1 @@
1
+ "use strict";var x=Object.defineProperty;var u=(e,t)=>x(e,"name",{value:t,configurable:!0});var f=Object.defineProperty,s=u((e,t)=>f(e,"name",{value:t,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.newExchangeId=exports.MAX_KEPT_BYTES=exports.REMOVED=void 0,exports.httpCaptureMode=httpCaptureMode,exports.redactHeaders=redactHeaders,exports.redactQuery=redactQuery,exports.keepBody=keepBody,exports.writeHttpExchange=writeHttpExchange;const tslib_1=require("tslib"),nodeFs=tslib_1.__importStar(require("node:fs")),node_crypto_1=require("node:crypto"),http_exchange_1=require("@lensmcp/core/http-exchange"),redact_1=require("@lensmcp/core/redact"),ai_store_1=require("./ai-store"),emit_1=require("./emit"),mkdirSync=nodeFs.mkdirSync,writeFileSync=nodeFs.writeFileSync,renameSync=nodeFs.renameSync,readdirSync=nodeFs.readdirSync,unlinkSync=nodeFs.unlinkSync;function httpCaptureMode(e=process.env){const t=(e.LENSMCP_HTTP_CAPTURE??"").trim().toLowerCase();return t==="off"||t==="0"||t==="false"||t==="none"?"off":t==="headers"?"headers":"bodies"}u(httpCaptureMode,"httpCaptureMode"),s(httpCaptureMode,"httpCaptureMode"),exports.REMOVED="\u2022\u2022\u2022\u2022 redacted",exports.MAX_KEPT_BYTES=64*1024;const MAX_PARSE_BYTES=2*1024*1024,newExchangeId=s(()=>`hx_${Date.now().toString(36)}${(0,node_crypto_1.randomBytes)(5).toString("hex")}`,"newExchangeId");exports.newExchangeId=newExchangeId;const CREDENTIAL_HEADERS=/^(authorization|proxy-authorization|cookie|set-cookie|x-api-key|api-key|openai-organization|openai-project|x-internal-token|x-amz-security-token)$|-api-key$|-token$|-secret$/i,CREDENTIAL_FIELD=/pass(word|wd)?|secret|token|api[-_]?key|authorization|credential|cookie|signature/i;function jwtClaims(e){const t=e.split(".");if(t.length===3)try{const r=JSON.parse(Buffer.from(t[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"));return`JWT \xB7 ${["iss","aud","sub","exp"].filter(o=>r[o]!==void 0).map(o=>`${o}=${o==="exp"&&typeof r[o]=="number"?new Date(r[o]*1e3).toISOString():String(r[o]).slice(0,80)}`).join(" \xB7 ")||"no claims"} \xB7 ${exports.REMOVED.split(" ")[0]} signature`}catch{return}}u(jwtClaims,"jwtClaims"),s(jwtClaims,"jwtClaims");function redactHeaders(e){const t=[],r=[];for(const[o,p]of e){const n=o.toLowerCase();if(CREDENTIAL_HEADERS.test(n)||(0,redact_1.isSecretParam)(n)){r.push(n);const i=/^bearer\s+(\S+)$/i.exec(p.trim()),c=i?jwtClaims(i[1]):void 0,a=/^(\w+)\s+\S/.exec(p.trim())?.[1];t.push([n,c?`Bearer ${c}`:a&&n.endsWith("authorization")?`${a} ${exports.REMOVED}`:exports.REMOVED])}else t.push([n,(0,ai_store_1.redactAiText)(p).slice(0,2048)])}return{headers:t,redacted:r}}u(redactHeaders,"redactHeaders"),s(redactHeaders,"redactHeaders");function redactQuery(e){const t=[];try{for(const[r,o]of new URLSearchParams(e))t.push([r,(0,redact_1.isSecretParam)(r)||CREDENTIAL_FIELD.test(r)?exports.REMOVED:(0,ai_store_1.redactAiText)(o).slice(0,1024)])}catch{}return t.slice(0,100)}u(redactQuery,"redactQuery"),s(redactQuery,"redactQuery");function redactJson(e,t,r,o=0){if(o>32||e===null||typeof e!="object")return typeof e=="string"?(0,ai_store_1.redactAiText)(e):e;if(Array.isArray(e))return e.map((n,i)=>redactJson(n,`${t}[${i}]`,r,o+1));const p={};for(const[n,i]of Object.entries(e)){const c=t?`${t}.${n}`:n;CREDENTIAL_FIELD.test(n)&&i!==null&&typeof i!="object"?(p[n]=exports.REMOVED,r.length<50&&r.push(c)):p[n]=redactJson(i,c,r,o+1)}return p}u(redactJson,"redactJson"),s(redactJson,"redactJson");const textual=s(e=>/json|^text\/|x-www-form-urlencoded|xml|javascript|graphql|event-stream/i.test(e)||e==="","textual");function keepBody(e,t,r,o,p){const n=e.split(";")[0].trim().toLowerCase();if(!textual(n))return{contentType:n||"application/octet-stream",size:r,kept:0,truncated:!1,omitted:"binary",...p?{sha256:p}:{}};const i=t.byteLength>=r&&r<=MAX_PARSE_BYTES,c=Buffer.from(t.buffer,t.byteOffset,t.byteLength).toString("utf8");let a=c;if(i&&/json/.test(n))try{a=JSON.stringify(redactJson(JSON.parse(c),"",o))}catch{a=(0,ai_store_1.redactAiText)(c)}else i&&/x-www-form-urlencoded/.test(n)?a=new URLSearchParams(redactQuery(c)).toString():a=(0,ai_store_1.redactAiText)(c);const d=Buffer.byteLength(a)>exports.MAX_KEPT_BYTES?Buffer.from(a).subarray(0,exports.MAX_KEPT_BYTES).toString("utf8"):a;return{contentType:n,size:r,kept:Buffer.byteLength(d),text:d,truncated:r>t.byteLength||d.length<a.length}}u(keepBody,"keepBody"),s(keepBody,"keepBody");const maxFiles=s(()=>{const e=Number(process.env.LENSMCP_HTTP_MAX_FILES);return Number.isInteger(e)&&e>=10?e:500},"maxFiles");let writes=0;function prune(e){try{const t=readdirSync(e).filter(r=>/^hx_[0-9a-z]+\.json$/.test(r)).sort();for(let r=0;r<t.length-maxFiles();r+=1)try{unlinkSync(`${e}/${t[r]}`)}catch{}}catch{}}u(prune,"prune"),s(prune,"prune");function writeHttpExchange(e){try{const t=(0,http_exchange_1.httpExchangeDir)(emit_1.EVENT_FILE);mkdirSync(t,{recursive:!0,mode:448});const r=(0,http_exchange_1.httpExchangeFile)(emit_1.EVENT_FILE,e.id);return writeFileSync(`${r}.tmp`,JSON.stringify(e),{mode:384}),renameSync(`${r}.tmp`,r),writes+=1,(writes===1||writes%25===0)&&prune(t),!0}catch{return!1}}u(writeHttpExchange,"writeHttpExchange"),s(writeHttpExchange,"writeHttpExchange");
@@ -1,3 +1,5 @@
1
+ /** Resolve once every exchange in flight is written — for tests, which read the files. */
2
+ export declare function flushEgressCaptureForTests(): Promise<void>;
1
3
  export declare function tapEgress(): void;
2
4
  /**
3
5
  * Restore the originals and clear the install markers.
package/lib/tap-egress.js CHANGED
@@ -1 +1 @@
1
- "use strict";var y=Object.defineProperty;var f=(t,s)=>y(t,"name",{value:s,configurable:!0});var g=Object.defineProperty,i=f((t,s)=>g(t,"name",{value:s,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.tapEgress=tapEgress,exports.resetEgressTapForTests=resetEgressTapForTests;const redact_1=require("@lensmcp/core/redact"),emit_1=require("./emit"),flow_context_1=require("./flow-context"),INTERNAL=[/\.local$/i,/\.internal$/i,/^localhost$/i,/^127\./,/^\[?::1\]?$/,/^0\.0\.0\.0$/,/^10\./,/^192\.168\./,/^172\.(1[6-9]|2\d|3[01])\./,/^169\.254\./,/^\[?[fF][cCdD][0-9a-fA-F]{2}:/,/^\[?[fF][eE]80:/],isInternal=i(t=>t===""||INTERNAL.some(s=>s.test(t)),"isInternal"),CAPTURE_QUERY=process.env.LENSMCP_EGRESS_QUERY==="1",MAX_PATH_CHARS=256,clipPath=i(t=>t.length>MAX_PATH_CHARS?`${t.slice(0,MAX_PATH_CHARS)}\u2026`:t,"clipPath");function capturePath(t,s){const e=t||"/";return!CAPTURE_QUERY||s.length<=1?clipPath(e):(0,redact_1.redactUrl)(`${e}${s}`)}f(capturePath,"capturePath"),i(capturePath,"capturePath");const fingerprintFor=i((t,s)=>s?`external:error:${t}`:`external:${t}`,"fingerprintFor");function tapEgress(){tapFetch(),tapHttpModule("http"),tapHttpModule("https")}f(tapEgress,"tapEgress"),i(tapEgress,"tapEgress");let savedFetch;const httpOriginals=new Map;function resetEgressTapForTests(){const t=globalThis;savedFetch&&(t.fetch=savedFetch),delete t.__lensmcpFetchTap,restoreHttp("http"),restoreHttp("https"),savedFetch=void 0}f(resetEgressTapForTests,"resetEgressTapForTests"),i(resetEgressTapForTests,"resetEgressTapForTests");function restoreHttp(t){const s=httpOriginals.get(t);if(!s)return;const e=require(`node:${t}`);e.request=s.request,e.get=s.get,httpOriginals.delete(t)}f(restoreHttp,"restoreHttp"),i(restoreHttp,"restoreHttp");function tapFetch(){const t=globalThis;if(typeof t.fetch!="function"||t.__lensmcpFetchTap)return;t.__lensmcpFetchTap=!0;const s=t.fetch.bind(globalThis);savedFetch=t.fetch,t.fetch=(async(e,a)=>{let r="",c="/",o=!1;try{const p=new URL(typeof e=="string"?e:e instanceof URL?e.href:e.url);r=p.hostname,o=p.search.length>1,c=capturePath(p.pathname,p.search)}catch{}if(isInternal(r))return s(e,a);const n=Date.now(),u=(0,flow_context_1.flowContext)(),h=(0,flow_context_1.liveFlowContext)();h&&(h.externalCallCount=(h.externalCallCount??0)+1);const d=typeof e=="object"&&e!==null&&!(e instanceof URL)?e.method:void 0,l=String(a?.method??d??"GET").toUpperCase();try{const p=await s(e,a),m=p.status>=500;return(0,emit_1.lensEmit)("external","info",`egress ${l} ${r} \u2192 ${p.status}`,{name:r,kind:"egress",method:l,host:r,path:c,status:p.status,...o?{hasQuery:!0}:{},startedAt:n,durationMs:Date.now()-n},fingerprintFor(r,m),u),p}catch(p){throw(0,emit_1.lensEmit)("external","error",`egress ${l} ${r} failed`,{name:r,kind:"egress",method:l,host:r,path:c,...o?{hasQuery:!0}:{},error:String(p?.message??p),startedAt:n,durationMs:Date.now()-n},fingerprintFor(r,!0),u),p}})}f(tapFetch,"tapFetch"),i(tapFetch,"tapFetch");function tapHttpModule(t){const s=require(`node:${t}`),e=s.request;if(typeof e!="function"||e.__lensmcp)return;httpOriginals.set(t,{request:e,get:s.get});const a=i(function(...r){const c=e.apply(this,r);try{observe(r,c)}catch{}return c},"wrapped");a.__lensmcp=!0,s.request=a,typeof s.get=="function"&&(s.get=function(...r){const c=a.apply(this,r);return c.end(),c})}f(tapHttpModule,"tapHttpModule"),i(tapHttpModule,"tapHttpModule");function observe(t,s){const{host:e,method:a,path:r,hasQuery:c}=parseTarget(t);if(isInternal(e))return;const o=Date.now(),n=(0,flow_context_1.flowContext)(),u=(0,flow_context_1.liveFlowContext)();u&&(u.externalCallCount=(u.externalCallCount??0)+1);let h=!1;s.once("response",(...d)=>{if(h)return;h=!0;const l=d[0]?.statusCode;(0,emit_1.lensEmit)("external","info",`egress ${a} ${e} \u2192 ${l??"?"}`,{name:e,kind:"egress",method:a,host:e,path:r,...l!=null?{status:l}:{},...c?{hasQuery:!0}:{},startedAt:o,durationMs:Date.now()-o},fingerprintFor(e,l!=null&&l>=500),n)}),s.once("error",(...d)=>{if(h)return;h=!0;const l=d[0];(0,emit_1.lensEmit)("external","error",`egress ${a} ${e} failed`,{name:e,kind:"egress",method:a,host:e,path:r,...c?{hasQuery:!0}:{},error:String(l?.message??l),startedAt:o,durationMs:Date.now()-o},fingerprintFor(e,!0),n)})}f(observe,"observe"),i(observe,"observe");function parseTarget(t){let s="",e="GET",a="/",r=!1;const c=i((o,n)=>{r=n.length>1,a=capturePath(o,n)},"take");for(const o of t)if(typeof o=="string")try{const n=new URL(o);s=n.hostname,c(n.pathname,n.search)}catch{}else if(o instanceof URL)s=o.hostname,c(o.pathname,o.search);else if(o&&typeof o=="object"){const n=o;if(typeof n.method=="string"&&(e=n.method.toUpperCase()),typeof n.socketPath=="string")return{host:"",method:e,path:a,hasQuery:r};if(typeof n.path=="string"){const u=n.path,h=u.indexOf("?");c(h===-1?u:u.slice(0,h),h===-1?"":u.slice(h))}typeof n.hostname=="string"&&n.hostname?s=n.hostname:typeof n.host=="string"&&n.host&&(s=n.host.split(":")[0]??"")}return{host:s,method:e,path:a,hasQuery:r}}f(parseTarget,"parseTarget"),i(parseTarget,"parseTarget");
1
+ "use strict";var F=Object.defineProperty;var l=(t,e)=>F(t,"name",{value:e,configurable:!0});var C=Object.defineProperty,i=l((t,e)=>C(t,"name",{value:e,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.flushEgressCaptureForTests=flushEgressCaptureForTests,exports.tapEgress=tapEgress,exports.resetEgressTapForTests=resetEgressTapForTests;const redact_1=require("@lensmcp/core/redact"),emit_1=require("./emit"),flow_context_1=require("./flow-context"),ai_providers_1=require("./ai-providers"),http_capture_1=require("./http-capture"),http_store_1=require("./http-store"),INTERNAL=[/\.local$/i,/\.internal$/i,/^localhost$/i,/^127\./,/^\[?::1\]?$/,/^0\.0\.0\.0$/,/^10\./,/^192\.168\./,/^172\.(1[6-9]|2\d|3[01])\./,/^169\.254\./,/^\[?[fF][cCdD][0-9a-fA-F]{2}:/,/^\[?[fF][eE]80:/],isInternal=i(t=>t===""||INTERNAL.some(e=>e.test(t)),"isInternal"),CAPTURE_QUERY=process.env.LENSMCP_EGRESS_QUERY==="1",MAX_PATH_CHARS=256,clipPath=i(t=>t.length>MAX_PATH_CHARS?`${t.slice(0,MAX_PATH_CHARS)}\u2026`:t,"clipPath");function capturePath(t,e){const s=t||"/";return!CAPTURE_QUERY||e.length<=1?clipPath(s):(0,redact_1.redactUrl)(`${s}${e}`)}l(capturePath,"capturePath"),i(capturePath,"capturePath");const fingerprintFor=i((t,e)=>e?`external:error:${t}`:`external:${t}`,"fingerprintFor"),pendingExchanges=new Set;async function flushEgressCaptureForTests(){await Promise.allSettled([...pendingExchanges])}l(flushEgressCaptureForTests,"flushEgressCaptureForTests"),i(flushEgressCaptureForTests,"flushEgressCaptureForTests");const headerPairs=i(t=>{const e=[];try{t?.headers?.forEach?.((s,d)=>e.push([d,s]))}catch{}return e},"headerPairs");async function finishExchange(t){const e=[],s=[],d=await(0,http_capture_1.requestPart)(t.request,t.url.search,t.mode,!!t.ai,s,e),r=t.reading?await t.reading.catch(()=>{}):void 0,{headers:c,redacted:a}=(0,http_store_1.redactHeaders)(headerPairs(t.res));e.push(...a);const n=typeof t.res?.status=="number"?t.res.status:void 0,p={schemaVersion:1,id:t.id,capturedAt:Date.now(),side:"egress",service:emit_1.PROJECT,...t.ctx?.flowId?{flowId:t.ctx.flowId}:{},...t.ctx?.requestId?{requestId:t.ctx.requestId}:{},method:t.method,url:`${t.url.protocol}//${t.url.host}${t.path}`,host:t.url.hostname,path:t.path,...n!==void 0?{status:n}:{},...t.res?.statusText?{statusText:t.res.statusText}:{},...t.error?{error:t.error}:{},startedAt:t.startedAt,...t.ttfbMs!==void 0?{ttfbMs:t.ttfbMs}:{},durationMs:(r?.endedAt??Date.now())-t.startedAt,capture:t.mode==="bodies"?"bodies":"headers",request:d,response:{headers:c,...r?.body?{body:r.body}:{},...r?.stream?{stream:r.stream}:{}},redacted:{headers:[...new Set(e)],bodyFields:[...s.map(o=>`request.${o}`),...t.responseFields.map(o=>`response.${o}`)]},...t.ai?{ai:{provider:t.ai}}:{}};(0,http_store_1.writeHttpExchange)(p)}l(finishExchange,"finishExchange"),i(finishExchange,"finishExchange");function trackExchange(t){const e=finishExchange(t).catch(()=>{});pendingExchanges.add(e),e.finally(()=>pendingExchanges.delete(e))}l(trackExchange,"trackExchange"),i(trackExchange,"trackExchange");function tapEgress(){tapFetch(),tapHttpModule("http"),tapHttpModule("https")}l(tapEgress,"tapEgress"),i(tapEgress,"tapEgress");let savedFetch;const httpOriginals=new Map;function resetEgressTapForTests(){const t=globalThis;savedFetch&&(t.fetch=savedFetch),delete t.__lensmcpFetchTap,restoreHttp("http"),restoreHttp("https"),savedFetch=void 0}l(resetEgressTapForTests,"resetEgressTapForTests"),i(resetEgressTapForTests,"resetEgressTapForTests");function restoreHttp(t){const e=httpOriginals.get(t);if(!e)return;const s=require(`node:${t}`);s.request=e.request,s.get=e.get,httpOriginals.delete(t)}l(restoreHttp,"restoreHttp"),i(restoreHttp,"restoreHttp");function tapFetch(){const t=globalThis;if(typeof t.fetch!="function"||t.__lensmcpFetchTap)return;t.__lensmcpFetchTap=!0;const e=t.fetch.bind(globalThis);savedFetch=t.fetch;const s=(0,http_store_1.httpCaptureMode)(),d=(0,ai_providers_1.parseExtraHosts)(process.env.LENSMCP_AI_HOSTS);t.fetch=(async(r,c)=>{let a="",n="/",p=!1,o;try{const h=new URL(typeof r=="string"?r:r instanceof URL?r.href:r.url);o=h,a=h.hostname,p=h.search.length>1,n=capturePath(h.pathname,h.search)}catch{}if(isInternal(a))return e(r,c);const f=Date.now(),u=(0,flow_context_1.flowContext)(),E=(0,flow_context_1.liveFlowContext)();E&&(E.externalCallCount=(E.externalCallCount??0)+1);const b=typeof r=="object"&&r!==null&&!(r instanceof URL)?r.method:void 0,m=String(c?.method??b??"GET").toUpperCase(),g=s!=="off"&&o?(0,http_store_1.newExchangeId)():void 0,v=g?(0,http_capture_1.takeRequest)(r,c,s==="bodies"):void 0,w=g&&o&&m==="POST"?(0,ai_providers_1.matchTarget)(a,o.port,o.pathname,d)?.provider:void 0,_=i(h=>({id:g,mode:s,method:m,url:o,path:o.pathname.length>256?`${o.pathname.slice(0,256)}\u2026`:o.pathname,startedAt:f,ctx:u,request:v,...w?{ai:w}:{},responseFields:[],...h}),"job");try{const h=await e(r,c),x=Date.now()-f,T=h.status>=500,$=[];let y;if(g&&s==="bodies")try{y=(0,http_capture_1.readResponse)(h,h.clone(),f,{ai:!!w,paths:$})}catch{y=void 0}const q=(0,emit_1.lensEmit)("external","info",`egress ${m} ${a} \u2192 ${h.status}`,{name:a,kind:"egress",method:m,host:a,path:n,status:h.status,...p?{hasQuery:!0}:{},startedAt:f,durationMs:x,...g?{exchangeId:g}:{}},fingerprintFor(a,T),u);return g&&q&&trackExchange(_({res:h,ttfbMs:x,responseFields:$,...y?{reading:y}:{}})),h}catch(h){const x=String(h?.message??h),T=(0,emit_1.lensEmit)("external","error",`egress ${m} ${a} failed`,{name:a,kind:"egress",method:m,host:a,path:n,...p?{hasQuery:!0}:{},error:x,startedAt:f,durationMs:Date.now()-f,...g?{exchangeId:g}:{}},fingerprintFor(a,!0),u);throw g&&T&&trackExchange(_({error:x})),h}})}l(tapFetch,"tapFetch"),i(tapFetch,"tapFetch");function tapHttpModule(t){const e=require(`node:${t}`),s=e.request;if(typeof s!="function"||s.__lensmcp)return;httpOriginals.set(t,{request:s,get:e.get});const d=i(function(...r){const c=s.apply(this,r);try{observe(r,c)}catch{}return c},"wrapped");d.__lensmcp=!0,e.request=d,typeof e.get=="function"&&(e.get=function(...r){const c=d.apply(this,r);return c.end(),c})}l(tapHttpModule,"tapHttpModule"),i(tapHttpModule,"tapHttpModule");function observe(t,e){const{host:s,method:d,path:r,hasQuery:c}=parseTarget(t);if(isInternal(s))return;const a=Date.now(),n=(0,flow_context_1.flowContext)(),p=(0,flow_context_1.liveFlowContext)();p&&(p.externalCallCount=(p.externalCallCount??0)+1);let o=!1;e.once("response",(...f)=>{if(o)return;o=!0;const u=f[0]?.statusCode;(0,emit_1.lensEmit)("external","info",`egress ${d} ${s} \u2192 ${u??"?"}`,{name:s,kind:"egress",method:d,host:s,path:r,...u!=null?{status:u}:{},...c?{hasQuery:!0}:{},startedAt:a,durationMs:Date.now()-a},fingerprintFor(s,u!=null&&u>=500),n)}),e.once("error",(...f)=>{if(o)return;o=!0;const u=f[0];(0,emit_1.lensEmit)("external","error",`egress ${d} ${s} failed`,{name:s,kind:"egress",method:d,host:s,path:r,...c?{hasQuery:!0}:{},error:String(u?.message??u),startedAt:a,durationMs:Date.now()-a},fingerprintFor(s,!0),n)})}l(observe,"observe"),i(observe,"observe");function parseTarget(t){let e="",s="GET",d="/",r=!1;const c=i((a,n)=>{r=n.length>1,d=capturePath(a,n)},"take");for(const a of t)if(typeof a=="string")try{const n=new URL(a);e=n.hostname,c(n.pathname,n.search)}catch{}else if(a instanceof URL)e=a.hostname,c(a.pathname,a.search);else if(a&&typeof a=="object"){const n=a;if(typeof n.method=="string"&&(s=n.method.toUpperCase()),typeof n.socketPath=="string")return{host:"",method:s,path:d,hasQuery:r};if(typeof n.path=="string"){const p=n.path,o=p.indexOf("?");c(o===-1?p:p.slice(0,o),o===-1?"":p.slice(o))}typeof n.hostname=="string"&&n.hostname?e=n.hostname:typeof n.host=="string"&&n.host&&(e=n.host.split(":")[0]??"")}return{host:e,method:s,path:d,hasQuery:r}}l(parseTarget,"parseTarget"),i(parseTarget,"parseTarget");
package/lib/tap-fs.d.ts CHANGED
@@ -1 +1,37 @@
1
+ export type FsOp = 'read' | 'write' | 'append';
2
+ /** One path in a window: its operation, how often and how many bytes, and the flow of its latest call. */
3
+ export interface FsPathOp {
4
+ path: string;
5
+ op: FsOp;
6
+ n: number;
7
+ bytes: number;
8
+ /** Opened as a stream: its bytes are not counted. */
9
+ stream?: true;
10
+ at: number;
11
+ flowId?: string;
12
+ requestId?: string;
13
+ }
14
+ export interface FsOpsPayload {
15
+ reads: number;
16
+ writes: number;
17
+ bytesRead: number;
18
+ bytesWritten: number;
19
+ top: string[];
20
+ paths: FsPathOp[];
21
+ }
22
+ /** One second of fs traffic: counted as it comes, drained into one `fs-ops` event. */
23
+ export declare class FsWindow {
24
+ private reads;
25
+ private writes;
26
+ private bytesRead;
27
+ private bytesWritten;
28
+ private readonly counts;
29
+ private readonly ops;
30
+ record(op: FsOp, path: string, bytes: number, at: number, ctx?: {
31
+ flowId?: string;
32
+ requestId?: string;
33
+ }, stream?: boolean): void;
34
+ /** The window as one event's payload — newest paths first — and a fresh window; nothing when it saw nothing. */
35
+ drain(): FsOpsPayload | undefined;
36
+ }
1
37
  export declare function tapFs(): void;
package/lib/tap-fs.js CHANGED
@@ -1 +1 @@
1
- "use strict";var d=Object.defineProperty;var c=(e,i)=>d(e,"name",{value:i,configurable:!0});var u=Object.defineProperty,l=c((e,i)=>u(e,"name",{value:i,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.tapFs=tapFs;const tslib_1=require("tslib"),os=tslib_1.__importStar(require("node:os")),emit_1=require("./emit"),bucket={reads:0,writes:0,bytesRead:0,bytesWritten:0,paths:new Map};let timer=null;const TMP=os.tmpdir(),CWD=process.cwd(),SKIP=["node_modules","/.nx/","/dist/","/.git/",".lensmcp"];function trackedPath(e){if((0,emit_1.isLensWriting)())return null;const i=typeof e=="string"?e:Buffer.isBuffer(e)?e.toString():e instanceof URL?e.pathname:null;return!i||i===emit_1.EVENT_FILE||i.startsWith(TMP)||SKIP.some(n=>i.includes(n))?null:i}c(trackedPath,"trackedPath"),l(trackedPath,"trackedPath");function record(e,i,n=0){const t=trackedPath(i);if(!t)return;e==="read"?(bucket.reads+=1,bucket.bytesRead+=n):(bucket.writes+=1,bucket.bytesWritten+=n);const r=t.startsWith(CWD)?t.slice(CWD.length+1):t;bucket.paths.size<50&&bucket.paths.set(r,(bucket.paths.get(r)??0)+1),timer||(timer=setTimeout(flush,1e3),timer.unref?.())}c(record,"record"),l(record,"record");function flush(){timer=null;const{reads:e,writes:i,bytesRead:n,bytesWritten:t}=bucket;if(e+i===0)return;const r=[...bucket.paths.entries()].sort((s,a)=>a[1]-s[1]).slice(0,3).map(([s])=>s);bucket.reads=bucket.writes=bucket.bytesRead=bucket.bytesWritten=0,bucket.paths.clear(),(0,emit_1.lensEmit)("fs","info",`fs ${e} reads \xB7 ${i} writes`,{name:"fs",kind:"fs-ops",reads:e,writes:i,bytesRead:n,bytesWritten:t,top:r},`fs:${emit_1.PROJECT}`)}c(flush,"flush"),l(flush,"flush");const byteLen=l(e=>typeof e=="string"?Buffer.byteLength(e):Buffer.isBuffer(e)?e.length:0,"byteLen");function wrap(e,i,n){const t=e?.[i];if(!e||typeof t!="function"||t.__lensmcp)return;const r=n(t);r.__lensmcp=!0;try{e[i]=r}catch{}}c(wrap,"wrap"),l(wrap,"wrap");function tapFs(){const e=require("node:fs");wrap(e,"readFileSync",n=>function(t,...r){const s=n.call(this,t,...r);return record("read",t,byteLen(s)),s}),wrap(e,"writeFileSync",n=>function(t,r,...s){const a=n.call(this,t,r,...s);return record("write",t,byteLen(r)),a}),wrap(e,"appendFileSync",n=>function(t,r,...s){const a=n.call(this,t,r,...s);return record("write",t,byteLen(r)),a}),wrap(e,"readFile",n=>function(t,...r){const s=r[r.length-1];return typeof s=="function"&&(r[r.length-1]=(a,o)=>{a||record("read",t,byteLen(o)),s(a,o)}),n.call(this,t,...r)});for(const n of["writeFile","appendFile"])wrap(e,n,t=>function(r,s,...a){const o=a[a.length-1];return typeof o=="function"&&(a[a.length-1]=f=>{f||record("write",r,byteLen(s)),o(f)}),t.call(this,r,s,...a)});const i=e.promises;wrap(i,"readFile",n=>async function(t,...r){const s=await n.call(this,t,...r);return record("read",t,byteLen(s)),s});for(const n of["writeFile","appendFile"])wrap(i,n,t=>async function(r,s,...a){const o=await t.call(this,r,s,...a);return record("write",r,byteLen(s)),o});wrap(e,"createReadStream",n=>function(t,...r){return record("read",t,0),n.call(this,t,...r)}),wrap(e,"createWriteStream",n=>function(t,...r){return record("write",t,0),n.call(this,t,...r)})}c(tapFs,"tapFs"),l(tapFs,"tapFs");
1
+ "use strict";var h=Object.defineProperty;var l=(e,i)=>h(e,"name",{value:i,configurable:!0});var d=Object.defineProperty,f=l((e,i)=>d(e,"name",{value:i,configurable:!0}),"f");Object.defineProperty(exports,"__esModule",{value:!0}),exports.FsWindow=void 0,exports.tapFs=tapFs;const tslib_1=require("tslib"),os=tslib_1.__importStar(require("node:os")),emit_1=require("./emit"),flow_context_1=require("./flow-context"),PATHS_KEPT=50,PATHS_SENT=20;class FsWindow{static{l(this,"FsWindow")}static{f(this,"FsWindow")}constructor(){this.reads=0,this.writes=0,this.bytesRead=0,this.bytesWritten=0,this.counts=new Map,this.ops=new Map}record(i,n,t,s,r,o=!1){i==="read"?(this.reads+=1,this.bytesRead+=t):(this.writes+=1,this.bytesWritten+=t),(this.counts.size<PATHS_KEPT||this.counts.has(n))&&this.counts.set(n,(this.counts.get(n)??0)+1);const c=`${i}\0${n}\0${r?.flowId??""}`,a=this.ops.get(c);a?(a.n+=1,a.bytes+=t,a.at=s,r?.requestId&&(a.requestId=r.requestId)):this.ops.size<PATHS_KEPT&&this.ops.set(c,{path:n,op:i,n:1,bytes:t,...o?{stream:!0}:{},at:s,...r?.flowId?{flowId:r.flowId}:{},...r?.requestId?{requestId:r.requestId}:{}})}drain(){const{reads:i,writes:n,bytesRead:t,bytesWritten:s}=this;if(i+n===0)return;const r=[...this.counts.entries()].sort((c,a)=>a[1]-c[1]).slice(0,3).map(([c])=>c),o=[...this.ops.values()].sort((c,a)=>a.at-c.at).slice(0,PATHS_SENT);return this.reads=this.writes=this.bytesRead=this.bytesWritten=0,this.counts.clear(),this.ops.clear(),{reads:i,writes:n,bytesRead:t,bytesWritten:s,top:r,paths:o}}}exports.FsWindow=FsWindow;const current=new FsWindow;let timer=null;const TMP=os.tmpdir(),CWD=process.cwd(),SKIP=["node_modules","/.nx/","/dist/","/.git/",".lensmcp"],CODE=/\.(?:[cm]?[jt]sx?|map|node)$/;let appending=0;function trackedPath(e){if((0,emit_1.isLensWriting)())return null;const i=typeof e=="string"?e:Buffer.isBuffer(e)?e.toString():e instanceof URL?e.pathname:null;return!i||i===emit_1.EVENT_FILE||i.startsWith(TMP)||SKIP.some(n=>i.includes(n))?null:i}l(trackedPath,"trackedPath"),f(trackedPath,"trackedPath");function record(e,i,n=0,t,s=!1){const r=trackedPath(i);if(!r||e==="read"&&CODE.test(r))return;const o=r.startsWith(CWD)?r.slice(CWD.length+1):r;current.record(e,o,n,Date.now(),t??(0,flow_context_1.flowContext)(),s),timer||(timer=setTimeout(flush,1e3),timer.unref?.())}l(record,"record"),f(record,"record");function flush(){timer=null;const e=current.drain();e&&(0,emit_1.lensEmit)("fs","info",`fs ${e.reads} reads \xB7 ${e.writes} writes`,{name:"fs",kind:"fs-ops",...e},`fs:${emit_1.PROJECT}`)}l(flush,"flush"),f(flush,"flush");const byteLen=f(e=>typeof e=="string"?Buffer.byteLength(e):Buffer.isBuffer(e)?e.length:0,"byteLen");function wrap(e,i,n){const t=e?.[i];if(!e||typeof t!="function"||t.__lensmcp)return;const s=n(t);s.__lensmcp=!0;try{e[i]=s}catch{}}l(wrap,"wrap"),f(wrap,"wrap");function tapFs(){const e=require("node:fs");wrap(e,"readFileSync",n=>function(t,...s){const r=n.call(this,t,...s);return record("read",t,byteLen(r)),r}),wrap(e,"writeFileSync",n=>function(t,s,...r){const o=appending>0,c=n.call(this,t,s,...r);return o||record("write",t,byteLen(s)),c}),wrap(e,"appendFileSync",n=>function(t,s,...r){appending++;let o;try{o=n.call(this,t,s,...r)}finally{appending--}return record("append",t,byteLen(s)),o}),wrap(e,"readFile",n=>function(t,...s){const r=s[s.length-1];if(typeof r=="function"){const o=(0,flow_context_1.flowContext)();s[s.length-1]=(c,a)=>{c||record("read",t,byteLen(a),o),r(c,a)}}return n.call(this,t,...s)});for(const n of["writeFile","appendFile"])wrap(e,n,t=>function(s,r,...o){const c=o[o.length-1],a=appending>0;if(typeof c=="function"){const p=(0,flow_context_1.flowContext)();o[o.length-1]=u=>{!u&&!a&&record(n==="appendFile"?"append":"write",s,byteLen(r),p),c(u)}}if(n!=="appendFile")return t.call(this,s,r,...o);appending++;try{return t.call(this,s,r,...o)}finally{appending--}});const i=e.promises;wrap(i,"readFile",n=>async function(t,...s){const r=(0,flow_context_1.flowContext)(),o=await n.call(this,t,...s);return record("read",t,byteLen(o),r),o});for(const n of["writeFile","appendFile"])wrap(i,n,t=>async function(s,r,...o){const c=(0,flow_context_1.flowContext)(),a=await t.call(this,s,r,...o);return record(n==="appendFile"?"append":"write",s,byteLen(r),c),a});wrap(e,"createReadStream",n=>function(t,...s){return record("read",t,0,(0,flow_context_1.flowContext)(),!0),n.call(this,t,...s)}),wrap(e,"createWriteStream",n=>function(t,...s){return record("write",t,0,(0,flow_context_1.flowContext)(),!0),n.call(this,t,...s)})}l(tapFs,"tapFs"),f(tapFs,"tapFs");
package/lib/tap-pg.js CHANGED
@@ -1 +1 @@
1
- "use strict";var P=Object.defineProperty;var f=(t,e)=>P(t,"name",{value:e,configurable:!0});var D=Object.defineProperty,r=f((t,e)=>D(t,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.pgTap=pgTap;const emit_1=require("./emit"),flow_context_1=require("./flow-context"),sql_accounting_1=require("./sql-accounting"),SKIP_OPS=new Set(["BEGIN","COMMIT","ROLLBACK","SET","SHOW","DEALLOCATE","DISCARD"]);function parseSql(t){const e=(t.match(/^\s*(\w+)/)?.[1]??"query").toUpperCase(),o=t.match(/\b(?:from|into|update|join|table(?:\s+if\s+not\s+exists)?)\s+"?([\w$.]+)"?/i)?.[1]?.split(".").pop();return{op:e,...o?{table:o}:{}}}f(parseSql,"parseSql"),r(parseSql,"parseSql");const oneLine=r(t=>t.replace(/\s+/g," ").trim(),"oneLine"),clip=r((t,e)=>t.length>e?t.slice(0,e-1)+"\u2026":t,"clip");function paramPreview(t){if(!(!Array.isArray(t)||t.length===0))return t.slice(0,10).map(e=>{if(e==null)return String(e);const o=typeof e=="string"?e:(()=>{try{return JSON.stringify(e)}catch{return String(e)}})();return clip(o,80)})}f(paramPreview,"paramPreview"),r(paramPreview,"paramPreview");const rowsOf=r(t=>t?.rowCount??t?.rows?.length??0,"rowsOf");function resultPreview(t){const e=t?.rows;if(!(!Array.isArray(e)||e.length===0))try{return clip(JSON.stringify(e.slice(0,2)),240)}catch{return}}f(resultPreview,"resultPreview"),r(resultPreview,"resultPreview");function pgTap(t){const e=t?.Client?.prototype,o=e?.query;if(!e||typeof o!="function")return!1;if(o.__lensmcp)return!0;const O=r(function(l,i,y){const u=l,g=typeof u=="string"?u:u?.text;if(!g||u&&typeof u=="object"&&typeof u.submit=="function")return o.call(this,l,i,y);const{op:v,table:a}=parseSql(g);if(SKIP_OPS.has(v))return o.call(this,l,i,y);const p=v.toLowerCase(),L=`db:${p}:${a??"query"}`,m=Date.now(),q=(0,flow_context_1.flowContext)(),w=(0,flow_context_1.liveFlowContext)();w&&(w.dbCallCount=(w.dbCallCount??0)+1);const b=(0,sql_accounting_1.beginSqlStatement)(w,q,m),A=clip(oneLine(g),500),S=paramPreview(Array.isArray(i)?i:l?.values),h=r((n,c)=>{const s=Date.now()-m;let C;if(n)C=(0,emit_1.lensEmitOutcome)("db","error",`${p}${a?" "+a:""} failed (${s}ms)`,{name:"postgres",kind:"db-query",op:p,...a?{table:a}:{},query:A,...S?{params:S}:{},startedAt:m,error:String(n?.message??n),durationMs:s},`db:error:${p}:${a??"query"}`,q);else{const $=rowsOf(c),x=resultPreview(c);C=(0,emit_1.lensEmitOutcome)("db","info",`${p} ${a??"query"} (${$} rows, ${s}ms)`,{name:"postgres",kind:"db-query",op:p,...a?{table:a}:{},query:A,...S?{params:S}:{},...x?{result:x}:{},startedAt:m,rows:$,durationMs:s},L,q)}(0,sql_accounting_1.endSqlStatement)(b,{durationMs:s,failed:!!n,emitted:C})},"done"),_=typeof y=="function"?y:typeof i=="function"?i:void 0;if(_){const n=r((c,s)=>{try{h(c,s)}catch{}_(c,s)},"wrappedCb");try{return typeof i=="function"?o.call(this,l,n):o.call(this,l,i,n)}catch(c){throw(0,sql_accounting_1.abandonSqlStatement)(b),c}}let d;try{d=o.call(this,l,i)}catch(n){throw(0,sql_accounting_1.abandonSqlStatement)(b),n}return d&&typeof d.then=="function"?d.then(n=>h(void 0,n),n=>h(n,void 0)):(0,sql_accounting_1.abandonSqlStatement)(b),d},"wrapped");return O.__lensmcp=!0,e.query=O,!0}f(pgTap,"pgTap"),r(pgTap,"pgTap");
1
+ "use strict";var R=Object.defineProperty;var p=(e,t)=>R(e,"name",{value:t,configurable:!0});var B=Object.defineProperty,o=p((e,t)=>B(e,"name",{value:t,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.pgTap=pgTap;const redact_1=require("@lensmcp/core/redact"),sql_shape_1=require("@lensmcp/core/sql-shape"),emit_1=require("./emit"),flow_context_1=require("./flow-context"),sql_accounting_1=require("./sql-accounting"),SKIP_OPS=new Set(["BEGIN","START","COMMIT","END","ROLLBACK","SAVEPOINT","RELEASE","SET","RESET","SHOW","DEALLOCATE","DISCARD","LISTEN","UNLISTEN"]),STATEMENT_EVERY_MS=10*6e4,STATEMENT_MAX=8*1024,statementSentAt=new Map,MAX_SHAPES_REMEMBERED=2e3,firstWord=o(e=>(/^\s*(?:(?:\/\*[\s\S]*?\*\/|--[^\n]*\n?)\s*)*(\w+)/.exec(e)?.[1]??"query").toUpperCase(),"firstWord");function parseSql(e){const t=(0,sql_shape_1.sqlShape)(e),r=(t.target??t.tables[0])?.split(".").pop();return{op:t.verb,...r?{table:r}:{},sqlId:t.id,tables:t.tables,...t.name?{name:t.name}:{}}}p(parseSql,"parseSql"),o(parseSql,"parseSql");const oneLine=o(e=>e.replace(/\s+/g," ").trim(),"oneLine"),clip=o((e,t)=>e.length>t?e.slice(0,t-1)+"\u2026":e,"clip");function paramPreview(e){if(!(!Array.isArray(e)||e.length===0))return e.slice(0,10).map(t=>{if(t==null)return String(t);const r=typeof t=="string"?t:(()=>{try{return JSON.stringify(t)}catch{return String(t)}})();return clip(r,80)})}p(paramPreview,"paramPreview"),o(paramPreview,"paramPreview");const rowsOf=o(e=>e?.rowCount??e?.rows?.length??0,"rowsOf");function resultPreview(e){const t=e?.rows;if(!(!Array.isArray(t)||t.length===0))try{return clip(JSON.stringify((0,redact_1.redactDeep)(t.slice(0,2))),240)}catch{return}}p(resultPreview,"resultPreview"),o(resultPreview,"resultPreview");function connectionOf(e){const t=e,r=t?.connectionParameters,m=typeof t?.database=="string"?t.database:typeof r?.database=="string"?r.database:void 0,a=typeof t?.host=="string"?t.host:typeof r?.host=="string"?r.host:void 0,s=typeof t?.port=="number"||typeof t?.port=="string"?t.port:typeof r?.port=="number"||typeof r?.port=="string"?r.port:5432;return{...m?{database:m}:{},...a?{server:`${a}:${s}`}:{}}}p(connectionOf,"connectionOf"),o(connectionOf,"connectionOf");function pgTap(e){const t=e?.Client?.prototype,r=t?.query;if(!t||typeof r!="function")return!1;if(r.__lensmcp)return!0;const m=o(function(a,s,b){const u=a,f=typeof u=="string"?u:u?.text;if(!f||u&&typeof u=="object"&&typeof u.submit=="function")return r.call(this,a,s,b);if(SKIP_OPS.has(firstWord(f)))return r.call(this,a,s,b);const{op:L,table:i,sqlId:q,tables:T,name:v}=parseSql(f),x=connectionOf(this),d=L.toLowerCase(),D=`db:${d}:${i??"query"}`,y=Date.now(),A=(0,flow_context_1.flowContext)(),g=(0,flow_context_1.liveFlowContext)();g&&(g.dbCallCount=(g.dbCallCount??0)+1);const w=(0,sql_accounting_1.beginSqlStatement)(g,A,y),O=clip(oneLine(f),500),C=paramPreview(Array.isArray(s)?s:a?.values),E=C&&(0,redact_1.redactSqlParams)(f,C),_=o((n,c)=>{const l=Date.now()-y,$=y-(statementSentAt.get(q)??-1/0)>=STATEMENT_EVERY_MS,P={sqlId:q,...T.length?{tables:T}:{},...v?{sqlName:v}:{},...x,...$?{statement:clip(f.trim(),STATEMENT_MAX)}:{}};let h;if(n)h=(0,emit_1.lensEmitOutcome)("db","error",`${d}${i?" "+i:""} failed (${l}ms)`,{name:"postgres",kind:"db-query",op:d,...i?{table:i}:{},...P,query:O,...E?{params:E}:{},startedAt:y,error:String(n?.message??n),durationMs:l},`db:error:${d}:${i??"query"}`,A);else{const N=rowsOf(c),I=resultPreview(c);h=(0,emit_1.lensEmitOutcome)("db","info",`${d} ${i??"query"} (${N} rows, ${l}ms)`,{name:"postgres",kind:"db-query",op:d,...i?{table:i}:{},...P,query:O,...E?{params:E}:{},...I?{result:I}:{},startedAt:y,rows:N,durationMs:l},D,A)}$&&h==="written"&&(statementSentAt.delete(q),statementSentAt.set(q,y),statementSentAt.size>MAX_SHAPES_REMEMBERED&&statementSentAt.delete(statementSentAt.keys().next().value)),(0,sql_accounting_1.endSqlStatement)(w,{durationMs:l,failed:!!n,emitted:h})},"done"),M=typeof b=="function"?b:typeof s=="function"?s:void 0;if(M){const n=o((c,l)=>{try{_(c,l)}catch{}M(c,l)},"wrappedCb");try{return typeof s=="function"?r.call(this,a,n):r.call(this,a,s,n)}catch(c){throw(0,sql_accounting_1.abandonSqlStatement)(w),c}}let S;try{S=r.call(this,a,s)}catch(n){throw(0,sql_accounting_1.abandonSqlStatement)(w),n}return S&&typeof S.then=="function"?S.then(n=>_(void 0,n),n=>_(n,void 0)):(0,sql_accounting_1.abandonSqlStatement)(w),S},"wrapped");return m.__lensmcp=!0,t.query=m,!0}p(pgTap,"pgTap"),o(pgTap,"pgTap");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/node-instrumentation",
3
- "version": "1.24.5",
3
+ "version": "1.25.0",
4
4
  "main": "./index.js",
5
5
  "types": "./index.d.ts",
6
6
  "exports": {
@@ -15,9 +15,9 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@lensmcp/core": "1.24.5",
19
- "@lensmcp/nest-instrumentation": "1.24.5",
20
- "@lensmcp/protocol-types": "1.24.5",
18
+ "@lensmcp/core": "1.25.0",
19
+ "@lensmcp/nest-instrumentation": "1.25.0",
20
+ "@lensmcp/protocol-types": "1.25.0",
21
21
  "tslib": "^2.3.0"
22
22
  },
23
23
  "optionalDependencies": {