@lensmcp/node-instrumentation 1.25.31 → 1.25.32
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/flow-context.d.ts +2 -0
- package/lib/http-capture.d.ts +6 -3
- package/lib/http-capture.js +2 -2
- package/lib/http-store.d.ts +13 -3
- package/lib/http-store.js +1 -1
- package/lib/tap-egress.js +1 -1
- package/lib/tap-ingress.js +1 -1
- package/package.json +4 -4
package/lib/flow-context.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ interface LiveCtx {
|
|
|
33
33
|
dbCallCount?: number;
|
|
34
34
|
redisCallCount?: number;
|
|
35
35
|
externalCallCount?: number;
|
|
36
|
+
/** Hops this request made to the cluster's own services (tap-egress `hopHeaders`, `nextHopHeaders`): `<request>.<n>`. */
|
|
37
|
+
hopCount?: number;
|
|
36
38
|
}
|
|
37
39
|
type CtxGetter = () => LiveCtx | undefined;
|
|
38
40
|
type CtxRunner = <T>(ctx: {
|
package/lib/http-capture.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface RequestSide {
|
|
|
16
16
|
omitted?: HttpExchangeBody['omitted'];
|
|
17
17
|
}
|
|
18
18
|
/** The request's headers and body, taken BEFORE the call. A stream or FormData is not read — it would be consumed. */
|
|
19
|
-
export declare function takeRequest(input: FetchInput, init: FetchInit, bodies: boolean): RequestSide;
|
|
19
|
+
export declare function takeRequest(input: FetchInput, init: FetchInit, bodies: boolean, large?: boolean): RequestSide;
|
|
20
20
|
/** A chunk as `http` passes it — a `Buffer`, or a string once the reader set an encoding — as bytes. */
|
|
21
21
|
export declare function chunkBytes(chunk: unknown, encoding?: unknown): Uint8Array | undefined;
|
|
22
22
|
/**
|
|
@@ -25,10 +25,11 @@ export declare function chunkBytes(chunk: unknown, encoding?: unknown): Uint8Arr
|
|
|
25
25
|
*/
|
|
26
26
|
export declare class WrittenBody {
|
|
27
27
|
private readonly keep;
|
|
28
|
+
private readonly large;
|
|
28
29
|
private readonly chunks;
|
|
29
30
|
private read;
|
|
30
31
|
size: number;
|
|
31
|
-
constructor(keep: boolean);
|
|
32
|
+
constructor(keep: boolean, large?: boolean);
|
|
32
33
|
take(chunk: unknown, encoding?: unknown): void;
|
|
33
34
|
/** Every byte of it was read. */
|
|
34
35
|
get whole(): boolean;
|
|
@@ -74,6 +75,7 @@ export declare class ResponseSink {
|
|
|
74
75
|
private readonly decoder;
|
|
75
76
|
constructor(contentType: string, startedAt: number, opts: {
|
|
76
77
|
ai: boolean;
|
|
78
|
+
large?: boolean;
|
|
77
79
|
paths: string[];
|
|
78
80
|
encoding?: string;
|
|
79
81
|
});
|
|
@@ -92,8 +94,9 @@ export declare class ResponseSink {
|
|
|
92
94
|
*/
|
|
93
95
|
export declare function readResponse(res: Response, clone: Response, startedAt: number, opts: {
|
|
94
96
|
ai: boolean;
|
|
97
|
+
large?: boolean;
|
|
95
98
|
paths: string[];
|
|
96
99
|
}): Promise<ResponseRead>;
|
|
97
100
|
/** The request side as kept: headers redacted, the query, the body (or why there is none). */
|
|
98
|
-
export declare function requestPart(side: RequestSide, search: string, mode: HttpCaptureMode, ai: boolean, paths: string[], redacted: string[]): Promise<HttpExchange['request']>;
|
|
101
|
+
export declare function requestPart(side: RequestSide, search: string, mode: HttpCaptureMode, ai: boolean, paths: string[], redacted: string[], large?: boolean): Promise<HttpExchange['request']>;
|
|
99
102
|
export {};
|
package/lib/http-capture.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var g=Object.defineProperty;var
|
|
2
|
-
`)}:void 0}
|
|
1
|
+
"use strict";var g=Object.defineProperty;var p=(t,e)=>g(t,"name",{value:e,configurable:!0});var l=Object.defineProperty,c=p((t,e)=>l(t,"name",{value:e,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.ResponseSink=exports.WrittenBody=exports.headerPairs=exports.MAX_STREAM_MS=void 0,exports.takeRequest=takeRequest,exports.chunkBytes=chunkBytes,exports.unpacked=unpacked,exports.writtenRequest=writtenRequest,exports.readResponse=readResponse,exports.requestPart=requestPart;const tslib_1=require("tslib"),node_crypto_1=require("node:crypto"),zlib=tslib_1.__importStar(require("node:zlib")),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;exports.MAX_STREAM_MS=300*1e3;const MAX_HASH_BYTES=1024*1024,MAX_READ=http_store_1.MAX_KEPT_BYTES*32,LIMITS={standard:{read:MAX_READ,keep:http_store_1.MAX_KEPT_BYTES,events:MAX_EVENTS,eventData:MAX_EVENT_DATA,streamKept:MAX_STREAM_KEPT},large:{read:http_store_1.MAX_AI_KEPT_BYTES,keep:http_store_1.MAX_AI_KEPT_BYTES,events:2e4,eventData:1024*1024,streamKept:8*1024*1024}},limitsOf=c(t=>t?LIMITS.large:LIMITS.standard,"limitsOf"),headerPairs=c(t=>{try{if(!t)return[];if(typeof t.forEach=="function"&&typeof t.get=="function"){const e=[];return t.forEach((s,n)=>e.push([n,s])),e}return Array.isArray(t)?t.map(([e,s])=>[String(e),String(s)]):Object.entries(t).map(([e,s])=>[e,Array.isArray(s)?s.join(", "):String(s)])}catch{return[]}},"headerPairs");exports.headerPairs=headerPairs;function takeRequest(t,e,s,n=!1){const r=typeof t=="object"&&t!==null&&!(t instanceof URL)?t:void 0,a=[...(0,exports.headerPairs)(r?.headers),...(0,exports.headerPairs)(e?.headers)],h=a.find(([d])=>d.toLowerCase()==="content-type")?.[1]??"",o={headers:a,contentType:h};if(!s)return o;const i=e?.body,u=c(d=>new TextEncoder().encode(d),"enc");if(typeof i=="string")return{...o,bytes:u(i),size:Buffer.byteLength(i)};if(i instanceof URLSearchParams)return{...o,contentType:h||"application/x-www-form-urlencoded",bytes:u(i.toString())};if(i instanceof ArrayBuffer)return{...o,bytes:new Uint8Array(i),size:i.byteLength};if(ArrayBuffer.isView(i))return{...o,bytes:new Uint8Array(i.buffer,i.byteOffset,i.byteLength),size:i.byteLength};if(typeof Blob<"u"&&i instanceof Blob)return{...o,size:i.size,...i.size<=limitsOf(n).read?{pending:i.arrayBuffer().then(d=>new Uint8Array(d)).catch(()=>{})}:{}};if(typeof FormData<"u"&&i instanceof FormData)return{...o,omitted:"form-data"};if(i!=null)return{...o,omitted:"stream"};if(r&&!r.bodyUsed&&r.body)try{return{...o,pending:r.clone().arrayBuffer().then(d=>new Uint8Array(d)).catch(()=>{})}}catch{return o}return o}p(takeRequest,"takeRequest"),c(takeRequest,"takeRequest");function chunkBytes(t,e){if(t instanceof Uint8Array)return t;if(typeof t=="string")return Buffer.from(t,typeof e=="string"&&Buffer.isEncoding(e)?e:"utf8")}p(chunkBytes,"chunkBytes"),c(chunkBytes,"chunkBytes");class WrittenBody{static{p(this,"WrittenBody")}static{c(this,"WrittenBody")}constructor(e,s=!1){this.keep=e,this.large=s,this.chunks=[],this.read=0,this.size=0}take(e,s){if(e==null||typeof e=="function")return;const n=chunkBytes(e,s);n&&(this.size+=n.byteLength,this.keep&&this.read<limitsOf(this.large).read&&(this.chunks.push(n),this.read+=n.byteLength))}get whole(){return this.read>=this.size}bytes(){return Buffer.concat(this.chunks)}}exports.WrittenBody=WrittenBody;function unpacked(t,e){try{if(t==="gzip"||t==="x-gzip")return zlib.gunzipSync(e,{maxOutputLength:MAX_READ});if(t==="br")return zlib.brotliDecompressSync(e,{maxOutputLength:MAX_READ});if(t==="deflate")return zlib.inflateSync(e,{maxOutputLength:MAX_READ})}catch{return}}p(unpacked,"unpacked"),c(unpacked,"unpacked");const encodingOf=c(t=>{const e=(t??"").trim().toLowerCase();return e==="identity"?"":e},"encodingOf");function writtenRequest(t,e){const s=(0,exports.headerPairs)(t),n=c(o=>s.find(([i])=>i.toLowerCase()===o)?.[1],"header"),r={headers:s,contentType:n("content-type")??""};if(!e||!e.size)return r;const a=encodingOf(n("content-encoding"));if(!a)return{...r,bytes:e.bytes(),size:e.size};const h=e.whole?unpacked(a,e.bytes()):void 0;return h?{...r,bytes:h,size:h.length}:{...r,size:e.size,omitted:"encoded"}}p(writtenRequest,"writtenRequest"),c(writtenRequest,"writtenRequest");function sseEvent(t){let e="message";const s=[];for(const n of t.split(/\r?\n/)){if(!n||n.startsWith(":"))continue;const r=n.indexOf(":"),a=r<0?n:n.slice(0,r),h=r<0?"":n.slice(r+1).replace(/^ /,"");a==="event"?e=h||"message":a==="data"&&s.push(h)}return s.length||e!=="message"?{event:e,data:s.join(`
|
|
2
|
+
`)}:void 0}p(sseEvent,"sseEvent"),c(sseEvent,"sseEvent");class ResponseSink{static{p(this,"ResponseSink")}static{c(this,"ResponseSink")}constructor(e,s,n){this.contentType=e,this.startedAt=s,this.opts=n,this.total=0,this.kept=[],this.keptBytes=0,this.hash=(0,node_crypto_1.createHash)("sha256"),this.events=[],this.eventTotal=0,this.eventData=0,this.cut=!1,this.pendingText="",this.decoder=new TextDecoder,this.encoding=encodingOf(n.encoding),this.sse=!this.encoding&&/text\/event-stream/i.test(e),this.parsing=this.sse,this.hashing=!/json|^text\/|xml|form/i.test(e)}get size(){return this.total}take(e){const s=sseEvent(e);if(!s)return;this.eventTotal++;const n=Date.now()-this.startedAt;this.firstEventMs??=n;const r=limitsOf(this.opts.large);if(this.events.length>=r.events||this.eventData>=r.streamKept){this.cut=!0;return}const a=this.opts.ai?void 0:(0,ai_store_1.redactAiText)(s.data).slice(0,r.eventData);a&&(this.eventData+=a.length),this.events.push({atMs:n,event:s.event,bytes:Buffer.byteLength(e),...a!==void 0?{data:a}:{}})}push(e){if(this.total+=e.byteLength,this.sse){if(!this.parsing)return this.inTime();this.pendingText+=this.decoder.decode(e,{stream:!0});let s;for(;(s=this.pendingText.search(/\r?\n\r?\n/))>=0;){const n=/\r?\n\r?\n/.exec(this.pendingText.slice(s));this.take(this.pendingText.slice(0,s)),this.pendingText=this.pendingText.slice(s+n[0].length)}this.pendingText.length>limitsOf(this.opts.large).read&&(this.pendingText="",this.parsing=!1,this.cut=!0)}else this.opts.ai||(this.keptBytes<limitsOf(this.opts.large).read&&(this.kept.push(e),this.keptBytes+=e.byteLength),this.hashing&&!this.encoding&&(this.total<=MAX_HASH_BYTES?this.hash.update(e):this.hashing=!1));return this.inTime()}inTime(){return Date.now()-this.startedAt<=exports.MAX_STREAM_MS?!0:(this.cut=!0,!1)}finish(e=!1,s=Date.now()){e?this.cut=!0:this.parsing&&this.pendingText.trim()&&this.take(this.pendingText);const n=this.total,r=this.contentType.split(";")[0].trim();if(this.sse)return{endedAt:s,stream:{protocol:"sse",...this.firstEventMs!==void 0?{firstEventMs:this.firstEventMs}:{},events:this.events,bytes:n,total:this.eventTotal,...this.cut?{cut:!0}:{}}};if(this.opts.ai)return{endedAt:s,body:{contentType:r,size:n,kept:0,truncated:!1,omitted:"ai"}};const a=Buffer.concat(this.kept);if(this.encoding){const o=n<=MAX_READ?unpacked(this.encoding,a):void 0;if(!o)return{endedAt:s,body:{contentType:r.toLowerCase(),size:n,kept:0,truncated:!1,omitted:"encoded"}};const i=this.hashing&&o.length<=MAX_HASH_BYTES?(0,node_crypto_1.createHash)("sha256").update(o).digest("hex"):void 0;return{endedAt:s,body:(0,http_store_1.keepBody)(this.contentType,o,o.length,this.opts.paths,i,limitsOf(this.opts.large).keep)}}const h=this.hashing&&n<=MAX_HASH_BYTES?this.hash.digest("hex"):void 0;return{endedAt:s,body:(0,http_store_1.keepBody)(this.contentType,a,n,this.opts.paths,h,limitsOf(this.opts.large).keep)}}}exports.ResponseSink=ResponseSink;function readResponse(t,e,s,n){const r=(()=>{try{return t.headers.get("content-type")??""}catch{return""}})(),a=e.body;if(!a)return Promise.resolve({endedAt:Date.now()});const h=a.getReader(),o=new ResponseSink(r,s,n);return(async()=>{try{for(;;){const{done:i,value:u}=await h.read();if(i)break;if(!o.push(u)){await h.cancel().catch(()=>{});break}}}catch{return o.finish(!0)}return o.finish()})()}p(readResponse,"readResponse"),c(readResponse,"readResponse");async function requestPart(t,e,s,n,r,a,h=!1){const{headers:o,redacted:i}=(0,http_store_1.redactHeaders)(t.headers);a.push(...i);const u=s==="bodies"&&e.length>1?(0,http_store_1.redactQuery)(e):[];let d;if(s==="bodies"){const y=t.contentType.split(";")[0].trim().toLowerCase();if(n)d={contentType:y,size:t.size??t.bytes?.byteLength??0,kept:0,truncated:!1,omitted:"ai"};else if(t.omitted)d={contentType:y,size:t.size??0,kept:0,truncated:!1,omitted:t.omitted};else{const f=t.bytes??await t.pending;f&&f.byteLength&&(d=(0,http_store_1.keepBody)(t.contentType,f,t.size??f.byteLength,r,void 0,limitsOf(h).keep))}}return{headers:o,...u.length?{query:u}:{},...d?{body:d}:{}}}p(requestPart,"requestPart"),c(requestPart,"requestPart");
|
package/lib/http-store.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ export declare function httpCaptureMode(env?: Record<string, string | undefined>
|
|
|
6
6
|
export declare const REMOVED = "\u2022\u2022\u2022\u2022 redacted";
|
|
7
7
|
/** A body keeps its first 64 KB as text. */
|
|
8
8
|
export declare const MAX_KEPT_BYTES: number;
|
|
9
|
+
/**
|
|
10
|
+
* An AI provider's call whose bodies are asked for keeps them whole, up to 4 MB each: its prompt and answer are what it
|
|
11
|
+
* is opened for, and a conversation's newest turns are at the END of its request — 64 KB kept its system prompt and cut
|
|
12
|
+
* the message being answered (FoodGuard: 729 of 1,932 requests, median 107 KB).
|
|
13
|
+
*/
|
|
14
|
+
export declare const MAX_AI_KEPT_BYTES: number;
|
|
9
15
|
export declare const newExchangeId: () => string;
|
|
10
16
|
/** Headers as kept: credential values removed (a bearer JWT shown as its claims), the names that were. */
|
|
11
17
|
export declare function redactHeaders(entries: ReadonlyArray<readonly [string, string]>): {
|
|
@@ -18,7 +24,7 @@ export declare function redactQuery(search: string): Array<[string, string]>;
|
|
|
18
24
|
* 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
25
|
* secret shapes — or, binary, its size (and digest). `bytes` is what was read; `size` every byte it had.
|
|
20
26
|
*/
|
|
21
|
-
export declare function keepBody(contentType: string, bytes: Uint8Array, size: number, paths: string[], sha256?: string): HttpExchangeBody;
|
|
27
|
+
export declare function keepBody(contentType: string, bytes: Uint8Array, size: number, paths: string[], sha256?: string, keep?: number): HttpExchangeBody;
|
|
22
28
|
/**
|
|
23
29
|
* How much of `.lensmcp/http/` is kept (issues/closed/foodguard-79). It was 500 files — about a minute at FoodGuard's
|
|
24
30
|
* ten exchanges a second, while the events that point at them stay on the bus for many minutes: a quarter of the
|
|
@@ -30,7 +36,11 @@ export declare const DEFAULT_HTTP_MAX_FILES = 10000;
|
|
|
30
36
|
export declare const DEFAULT_HTTP_MAX_AGE_MS: number;
|
|
31
37
|
/** When an exchange was written — its id starts with the time (`newExchangeId`), so no file needs a stat. */
|
|
32
38
|
export declare const exchangeTime: (file: string) => number | undefined;
|
|
33
|
-
/**
|
|
34
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Oldest first, by name (= by time). AI exchanges are retained even beyond the count/age budgets: they live in `ai/`,
|
|
41
|
+
* which is never pruned; one written here before that place existed is moved there the first time a prune meets it,
|
|
42
|
+
* so it is opened once — not on every prune, for good.
|
|
43
|
+
*/
|
|
44
|
+
export declare function pruneHttpExchanges(dir: string, now?: number, aiDir?: string): number;
|
|
35
45
|
/** Persist one exchange (temp name, then rename). Best-effort: false on any failure — the call goes on regardless. */
|
|
36
46
|
export declare function writeHttpExchange(x: HttpExchange): boolean;
|
package/lib/http-store.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var _=Object.defineProperty;var u=(e,t)=>_(e,"name",{value:t,configurable:!0});var x=Object.defineProperty,a=u((e,t)=>x(e,"name",{value:t,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.exchangeTime=exports.DEFAULT_HTTP_MAX_AGE_MS=exports.DEFAULT_HTTP_MAX_FILES=exports.newExchangeId=exports.MAX_AI_KEPT_BYTES=exports.MAX_KEPT_BYTES=exports.REMOVED=void 0,exports.httpCaptureMode=httpCaptureMode,exports.redactHeaders=redactHeaders,exports.redactQuery=redactQuery,exports.keepBody=keepBody,exports.pruneHttpExchanges=pruneHttpExchanges,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,readFileSync=nodeFs.readFileSync,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"),a(httpCaptureMode,"httpCaptureMode"),exports.REMOVED="\u2022\u2022\u2022\u2022 redacted",exports.MAX_KEPT_BYTES=64*1024,exports.MAX_AI_KEPT_BYTES=4*1024*1024;const MAX_PARSE_BYTES=2*1024*1024,newExchangeId=a(()=>`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(n=>r[n]!==void 0).map(n=>`${n}=${n==="exp"&&typeof r[n]=="number"?new Date(r[n]*1e3).toISOString():String(r[n]).slice(0,80)}`).join(" \xB7 ")||"no claims"} \xB7 ${exports.REMOVED.split(" ")[0]} signature`}catch{return}}u(jwtClaims,"jwtClaims"),a(jwtClaims,"jwtClaims");function redactHeaders(e){const t=[],r=[];for(const[n,o]of e){const s=n.toLowerCase();if(CREDENTIAL_HEADERS.test(s)||(0,redact_1.isSecretParam)(s)){r.push(s);const c=/^bearer\s+(\S+)$/i.exec(o.trim()),i=c?jwtClaims(c[1]):void 0,p=/^(\w+)\s+\S/.exec(o.trim())?.[1];t.push([s,i?`Bearer ${i}`:p&&s.endsWith("authorization")?`${p} ${exports.REMOVED}`:exports.REMOVED])}else t.push([s,(0,ai_store_1.redactAiText)(o).slice(0,2048)])}return{headers:t,redacted:r}}u(redactHeaders,"redactHeaders"),a(redactHeaders,"redactHeaders");function redactQuery(e){const t=[];try{for(const[r,n]of new URLSearchParams(e))t.push([r,(0,redact_1.isSecretParam)(r)||CREDENTIAL_FIELD.test(r)?exports.REMOVED:(0,ai_store_1.redactAiText)(n).slice(0,1024)])}catch{}return t.slice(0,100)}u(redactQuery,"redactQuery"),a(redactQuery,"redactQuery");function redactJson(e,t,r,n=0){if(n>32||e===null||typeof e!="object")return typeof e=="string"?(0,ai_store_1.redactAiText)(e):e;if(Array.isArray(e))return e.map((s,c)=>redactJson(s,`${t}[${c}]`,r,n+1));const o={};for(const[s,c]of Object.entries(e)){const i=t?`${t}.${s}`:s,p=typeof c=="number"&&/tokens/i.test(s);CREDENTIAL_FIELD.test(s)&&c!==null&&typeof c!="object"&&!p?(o[s]=exports.REMOVED,r.length<50&&r.push(i)):o[s]=redactJson(c,i,r,n+1)}return o}u(redactJson,"redactJson"),a(redactJson,"redactJson");const textual=a(e=>/json|^text\/|x-www-form-urlencoded|xml|javascript|graphql|event-stream/i.test(e)||e==="","textual");function keepBody(e,t,r,n,o,s=exports.MAX_KEPT_BYTES){const c=e.split(";")[0].trim().toLowerCase();if(!textual(c))return{contentType:c||"application/octet-stream",size:r,kept:0,truncated:!1,omitted:"binary",...o?{sha256:o}:{}};const i=t.byteLength>=r&&r<=Math.max(MAX_PARSE_BYTES,s),p=Buffer.from(t.buffer,t.byteOffset,t.byteLength).toString("utf8");let E;if(i&&/json/.test(c))try{E=JSON.stringify(redactJson(JSON.parse(p),"",n))}catch{E=(0,ai_store_1.redactAiText)(p)}else i&&/x-www-form-urlencoded/.test(c)?E=new URLSearchParams(redactQuery(p)).toString():E=(0,ai_store_1.redactAiText)(p);const d=Buffer.byteLength(E)>s?Buffer.from(E).subarray(0,s).toString("utf8"):E;return{contentType:c,size:r,kept:Buffer.byteLength(d),text:d,truncated:r>t.byteLength||d.length<E.length}}u(keepBody,"keepBody"),a(keepBody,"keepBody"),exports.DEFAULT_HTTP_MAX_FILES=1e4,exports.DEFAULT_HTTP_MAX_AGE_MS=1440*6e4;const PRUNE_EVERY_MS=3e4,maxFiles=a(()=>{const e=Number(process.env.LENSMCP_HTTP_MAX_FILES);return Number.isInteger(e)&&e>=10?e:exports.DEFAULT_HTTP_MAX_FILES},"maxFiles"),maxAgeMs=a(()=>{const e=Number(process.env.LENSMCP_HTTP_MAX_AGE_MS);return Number.isFinite(e)&&e>=6e4?e:exports.DEFAULT_HTTP_MAX_AGE_MS},"maxAgeMs"),exchangeTime=a(e=>{const t=/^hx_([0-9a-z]{8})/.exec(e),r=t?parseInt(t[1],36):NaN;return Number.isFinite(r)?r:void 0},"exchangeTime");exports.exchangeTime=exchangeTime;const lastPrune=new Map;function pruneHttpExchanges(e,t=Date.now(),r=`${e}/ai`){let n=0;try{const o=readdirSync(e).filter(i=>/^hx_[0-9a-z]+\.json$/.test(i)).sort(),s=o.length-maxFiles(),c=t-maxAgeMs();for(let i=0;i<o.length;i+=1){const p=(0,exports.exchangeTime)(o[i]);if(i>=s&&(p===void 0||p>=c))break;try{if(JSON.parse(readFileSync(`${e}/${o[i]}`,"utf8"))?.ai){mkdirSync(r,{recursive:!0,mode:448}),renameSync(`${e}/${o[i]}`,`${r}/${o[i]}`);continue}unlinkSync(`${e}/${o[i]}`),n+=1}catch{}}}catch{}return n}u(pruneHttpExchanges,"pruneHttpExchanges"),a(pruneHttpExchanges,"pruneHttpExchanges");function writeHttpExchange(e){try{const t=(0,http_exchange_1.exchangePlace)(e),r=(0,http_exchange_1.httpExchangeDir)(emit_1.EVENT_FILE,t);mkdirSync(r,{recursive:!0,mode:448});const n=(0,http_exchange_1.httpExchangeFile)(emit_1.EVENT_FILE,e.id,t);writeFileSync(`${n}.tmp`,JSON.stringify(e),{mode:384}),renameSync(`${n}.tmp`,n);const o=Date.now();return t!=="ai"&&o-(lastPrune.get(t)??0)>=PRUNE_EVERY_MS&&(lastPrune.set(t,o),pruneHttpExchanges(r,o,(0,http_exchange_1.httpExchangeDir)(emit_1.EVENT_FILE,"ai"))),!0}catch{return!1}}u(writeHttpExchange,"writeHttpExchange"),a(writeHttpExchange,"writeHttpExchange");
|
package/lib/tap-egress.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var P=Object.defineProperty;var x=(t,e)=>P(t,"name",{value:e,configurable:!0});var H=Object.defineProperty,c=x((t,e)=>H(t,"name",{value:e,configurable:!0}),"c");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"),capture_file_1=require("./capture-file"),tap_ai_1=require("./tap-ai"),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=c(t=>t===""||INTERNAL.some(e=>e.test(t)),"isInternal"),CAPTURE_QUERY=process.env.LENSMCP_EGRESS_QUERY==="1",MAX_PATH_CHARS=256,clipPath=c(t=>t.length>MAX_PATH_CHARS?`${t.slice(0,MAX_PATH_CHARS)}\u2026`:t,"clipPath");function capturePath(t,e){const r=t||"/";return!CAPTURE_QUERY||e.length<=1?clipPath(r):(0,redact_1.redactUrl)(`${r}${e}`)}x(capturePath,"capturePath"),c(capturePath,"capturePath");const fingerprintFor=c((t,e)=>e?`external:error:${t}`:`external:${t}`,"fingerprintFor"),pendingExchanges=new Set;async function flushEgressCaptureForTests(){await Promise.allSettled([...pendingExchanges])}x(flushEgressCaptureForTests,"flushEgressCaptureForTests"),c(flushEgressCaptureForTests,"flushEgressCaptureForTests");async function finishExchange(t){const e=[],r=[],d=await(0,http_capture_1.requestPart)(t.request,t.url.search,t.mode,!!t.aiOpaque,r,e),a=t.reading?await t.reading.catch(()=>{}):void 0,{headers:u,redacted:s}=(0,http_store_1.redactHeaders)(t.responseHeaders??[]);e.push(...s);const o=typeof t.status=="number"?t.status:void 0,g={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,...o!==void 0?{status:o}:{},...t.statusText?{statusText:t.statusText}:{},...t.error?{error:t.error}:{},startedAt:t.startedAt,...t.ttfbMs!==void 0?{ttfbMs:t.ttfbMs}:{},durationMs:(a?.endedAt??Date.now())-t.startedAt,capture:t.mode==="bodies"?"bodies":"headers",request:d,response:{headers:u,...a?.body?{body:a.body}:{},...a?.stream?{stream:a.stream}:{}},redacted:{headers:[...new Set(e)],bodyFields:[...r.map(i=>`request.${i}`),...t.responseFields.map(i=>`response.${i}`)]},...t.ai?{ai:{provider:t.ai}}:{}};(0,http_store_1.writeHttpExchange)(g)}x(finishExchange,"finishExchange"),c(finishExchange,"finishExchange");function trackExchange(t){const e=(typeof t=="function"?new Promise(r=>setImmediate(r)).then(()=>finishExchange(t())):finishExchange(t)).catch(()=>{});pendingExchanges.add(e),e.finally(()=>pendingExchanges.delete(e))}x(trackExchange,"trackExchange"),c(trackExchange,"trackExchange");function tapEgress(){tapFetch(),tapHttpModule("http"),tapHttpModule("https")}x(tapEgress,"tapEgress"),c(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}x(resetEgressTapForTests,"resetEgressTapForTests"),c(resetEgressTapForTests,"resetEgressTapForTests");function restoreHttp(t){const e=httpOriginals.get(t);if(!e)return;const r=require(`node:${t}`);r.request=e.request,r.get=e.get,httpOriginals.delete(t)}x(restoreHttp,"restoreHttp"),c(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 r=(0,http_store_1.httpCaptureMode)(),d=(0,ai_providers_1.parseExtraHosts)(process.env.LENSMCP_AI_HOSTS);t.fetch=(async(a,u)=>{let s="",o="/",g=!1,i;try{const h=new URL(typeof a=="string"?a:a instanceof URL?a.href:a.url);i=h,s=h.hostname,g=h.search.length>1,o=capturePath(h.pathname,h.search)}catch{}if(isInternal(s))return e(a,u);const l=Date.now(),f=(0,flow_context_1.flowContext)(),n=(0,flow_context_1.liveFlowContext)();n&&(n.externalCallCount=(n.externalCallCount??0)+1);const w=typeof a=="object"&&a!==null&&!(a instanceof URL)?a.method:void 0,p=String(u?.method??w??"GET").toUpperCase(),E=r!=="off"&&i?(0,http_store_1.newExchangeId)():void 0,b=E?(0,http_capture_1.takeRequest)(a,u,r==="bodies"):void 0,q=E&&i&&p==="POST"?(0,ai_providers_1.matchTarget)(s,i.port,i.pathname,d)?.provider:void 0,T=!!q&&(0,tap_ai_1.aiCaptureMode)()!=="content"&&!(0,capture_file_1.aiBodiesAsked)(),_=c(h=>({id:E,mode:r,method:p,url:i,path:i.pathname.length>256?`${i.pathname.slice(0,256)}\u2026`:i.pathname,startedAt:l,ctx:f,request:b,...q?{ai:q}:{},...T?{aiOpaque:T}:{},responseFields:[],...h}),"job");try{const h=await e(a,u),$=Date.now()-l,C=h.status>=500,m=[];let y;if(E&&r==="bodies")try{y=(0,http_capture_1.readResponse)(h,h.clone(),l,{ai:T,paths:m})}catch{y=void 0}const v=(0,emit_1.lensEmit)("external","info",`egress ${p} ${s} \u2192 ${h.status}`,{name:s,kind:"egress",method:p,host:s,path:o,status:h.status,...g?{hasQuery:!0}:{},startedAt:l,durationMs:$,...E?{exchangeId:E}:{}},fingerprintFor(s,C),f);if(E&&v){const A={status:h.status,...h.statusText?{statusText:h.statusText}:{},responseHeaders:(0,http_capture_1.headerPairs)(h.headers)};trackExchange(_({...A,ttfbMs:$,responseFields:m,...y?{reading:y}:{}}))}return h}catch(h){const $=String(h?.message??h),C=(0,emit_1.lensEmit)("external","error",`egress ${p} ${s} failed`,{name:s,kind:"egress",method:p,host:s,path:o,...g?{hasQuery:!0}:{},error:$,startedAt:l,durationMs:Date.now()-l,...E?{exchangeId:E}:{}},fingerprintFor(s,!0),f);throw E&&C&&trackExchange(_({error:$})),h}})}x(tapFetch,"tapFetch"),c(tapFetch,"tapFetch");function tapHttpModule(t){const e=require(`node:${t}`),r=e.request;if(typeof r!="function"||r.__lensmcp)return;httpOriginals.set(t,{request:r,get:e.get});const d=(0,http_store_1.httpCaptureMode)(),a=(0,ai_providers_1.parseExtraHosts)(process.env.LENSMCP_AI_HOSTS),u=c(function(...s){const o=r.apply(this,s);try{observe(t,s,o,d,a)}catch{}return o},"wrapped");u.__lensmcp=!0,e.request=u,typeof e.get=="function"&&(e.get=function(...s){const o=u.apply(this,s);return o.end(),o})}x(tapHttpModule,"tapHttpModule"),c(tapHttpModule,"tapHttpModule");const headerOf=c((t,e)=>{const r=t?.[e];return Array.isArray(r)?r.join(", "):r??""},"headerOf");function teeRequest(t,e,r){const d=e?new http_capture_1.WrittenBody(!r):void 0,a=c(()=>{try{return t.getHeaders?.()}catch{return}},"sentHeaders");let u,s=!1;const o=t.write;d&&typeof o=="function"&&(t.write=function(i,...l){try{s||d.take(i,l[0])}catch{}return o.call(this,i,...l)});const g=t.end;return t.end=function(i,...l){try{s||(s=!0,d?.take(i,l[0]),u=a())}catch{}return g.call(this,i,...l)},()=>(0,http_capture_1.writtenRequest)(u??a(),d)}x(teeRequest,"teeRequest"),c(teeRequest,"teeRequest");function teeResponse(t,e,r){let d=!1;const a=c(o=>{if(d)return;d=!0,clearTimeout(u);const g=Date.now();r(()=>e.size?e.finish(o,g):{endedAt:g})},"finish"),u=setTimeout(()=>a(!0),http_capture_1.MAX_STREAM_MS);u.unref?.();const s=t.emit;t.emit=function(o,...g){try{return s.call(this,o,...g)}finally{try{if(o==="data"){const i=d?void 0:(0,http_capture_1.chunkBytes)(g[0],t.readableEncoding);i&&!e.push(i)&&a(!1)}else o==="end"?a(!1):(o==="aborted"||o==="error"||o==="close")&&a(!0)}catch{}}}}x(teeResponse,"teeResponse"),c(teeResponse,"teeResponse");function observe(t,e,r,d,a){const u=parseTarget(e,t),{host:s,method:o,path:g,hasQuery:i}=u;if(isInternal(s))return;const l=Date.now(),f=(0,flow_context_1.flowContext)(),n=(0,flow_context_1.liveFlowContext)();n&&(n.externalCallCount=(n.externalCallCount??0)+1);const w=d!=="off"?targetUrl(u):void 0,p=w?(0,http_store_1.newExchangeId)():void 0,E=p&&w&&o==="POST"?(0,ai_providers_1.matchTarget)(s,w.port,w.pathname,a)?.provider:void 0,b=!!E&&(0,tap_ai_1.aiCaptureMode)()!=="content"&&!(0,capture_file_1.aiBodiesAsked)(),q=p?teeRequest(r,d==="bodies",b):void 0,T=c(m=>({id:p,mode:d,method:o,url:w,path:clipPath(w.pathname),startedAt:l,ctx:f,request:q(),...E?{ai:E}:{},...b?{aiOpaque:b}:{},responseFields:[],...m}),"job");let _=!1;const h=c(m=>{if(_)return;_=!0;const y=m?.statusCode,v=Date.now()-l,A=(0,emit_1.lensEmit)("external","info",`egress ${o} ${s} \u2192 ${y??"?"}`,{name:s,kind:"egress",method:o,host:s,path:g,...y!=null?{status:y}:{},...i?{hasQuery:!0}:{},startedAt:l,durationMs:v,...p?{exchangeId:p}:{}},fingerprintFor(s,y!=null&&y>=500),f);if(!p||!A||!m||typeof m.emit!="function")return;const M={...y!=null?{status:y}:{},...m.statusMessage?{statusText:m.statusMessage}:{},responseHeaders:(0,http_capture_1.headerPairs)(m.headers),ttfbMs:v};if(d!=="bodies"){trackExchange(()=>T(M));return}const F=[],R=new http_capture_1.ResponseSink(headerOf(m.headers,"content-type"),l,{ai:b,paths:F,encoding:headerOf(m.headers,"content-encoding")});teeResponse(m,R,S=>trackExchange(()=>T({...M,responseFields:F,reading:Promise.resolve(S())})))},"onResponse"),$=c(m=>{if(_)return;_=!0;const y=String(m?.message??m),v=(0,emit_1.lensEmit)("external","error",`egress ${o} ${s} failed`,{name:s,kind:"egress",method:o,host:s,path:g,...i?{hasQuery:!0}:{},error:y,startedAt:l,durationMs:Date.now()-l,...p?{exchangeId:p}:{}},fingerprintFor(s,!0),f);p&&v&&trackExchange(()=>T({error:y}))},"onError"),C=r.emit;r.emit=function(m,...y){try{m==="response"?h(y[0]):m==="error"&&$(y[0])}catch{}return C.call(this,m,...y)}}x(observe,"observe"),c(observe,"observe");function targetUrl(t){try{const e=t.host.includes(":")&&!t.host.startsWith("[")?`[${t.host}]`:t.host,r=t.pathname.startsWith("/")?t.pathname:`/${t.pathname}`;return new URL(`${t.protocol}//${e}${t.port?`:${t.port}`:""}${r}${t.search}`)}catch{return}}x(targetUrl,"targetUrl"),c(targetUrl,"targetUrl");function parseTarget(t,e="http"){let r="",d="GET",a="/",u=!1,s=`${e}:`,o="",g="/",i="";const l=c((f,n)=>{u=n.length>1,a=capturePath(f,n),g=f||"/",i=n},"take");for(const f of t)if(typeof f=="string")try{const n=new URL(f);r=n.hostname,s=n.protocol,o=n.port,l(n.pathname,n.search)}catch{}else if(f instanceof URL)r=f.hostname,s=f.protocol,o=f.port,l(f.pathname,f.search);else if(f&&typeof f=="object"){const n=f;if(typeof n.method=="string"&&(d=n.method.toUpperCase()),typeof n.socketPath=="string")return{host:"",method:d,path:a,hasQuery:u,protocol:s,port:o,pathname:g,search:i};if(typeof n.path=="string"){const w=n.path,p=w.indexOf("?");l(p===-1?w:w.slice(0,p),p===-1?"":w.slice(p))}if(typeof n.protocol=="string"&&n.protocol&&(s=n.protocol),(typeof n.port=="number"||typeof n.port=="string"&&n.port)&&(o=String(n.port)),typeof n.hostname=="string"&&n.hostname)r=n.hostname;else if(typeof n.host=="string"&&n.host){const[w,p]=n.host.split(":");r=w??"",p&&!(typeof n.port=="number"||typeof n.port=="string")&&(o=p)}}return{host:r,method:d,path:a,hasQuery:u,protocol:s,port:o,pathname:g,search:i}}x(parseTarget,"parseTarget"),c(parseTarget,"parseTarget");
|
|
1
|
+
"use strict";var O=Object.defineProperty;var E=(t,e)=>O(t,"name",{value:e,configurable:!0});var v=Object.defineProperty,d=E((t,e)=>v(t,"name",{value:e,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.flushEgressCaptureForTests=flushEgressCaptureForTests,exports.tapEgress=tapEgress,exports.resetEgressTapForTests=resetEgressTapForTests;const redact_1=require("@lensmcp/core/redact"),http_exchange_1=require("@lensmcp/core/http-exchange"),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"),capture_file_1=require("./capture-file"),tap_ai_1=require("./tap-ai"),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=d(t=>t===""||INTERNAL.some(e=>e.test(t)),"isInternal"),CAPTURE_QUERY=process.env.LENSMCP_EGRESS_QUERY==="1",MAX_PATH_CHARS=256,clipPath=d(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}`)}E(capturePath,"capturePath"),d(capturePath,"capturePath");const fingerprintFor=d((t,e)=>e?`external:error:${t}`:`external:${t}`,"fingerprintFor"),PARENT_HEADER="x-lensmcp-parent-request-id",FLOW_HEADER="x-lensmcp-flow-id",ORIGIN_HEADER="x-lensmcp-origin-node-id";let hopsOutside=0;function hopHeaders(t,e){if(!t?.requestId&&!t?.flowId||e(http_exchange_1.LENS_REQUEST_ID_HEADER)||e(PARENT_HEADER)||e(FLOW_HEADER))return;const s={};if(t.requestId){const i=(0,flow_context_1.liveFlowContext)(),r=i?i.hopCount=(i.hopCount??0)+1:++hopsOutside;s[http_exchange_1.LENS_REQUEST_ID_HEADER]=`${t.requestId}.${r}`,s[PARENT_HEADER]=t.requestId}return t.flowId&&(s[FLOW_HEADER]=t.flowId),t.originNodeId&&(s[ORIGIN_HEADER]=t.originNodeId),s}E(hopHeaders,"hopHeaders"),d(hopHeaders,"hopHeaders");function stampFetch(t,e,s){try{const i=e?.headers??(typeof t=="object"&&t!==null&&!(t instanceof URL)?t.headers:void 0),r=new Headers(i),u=hopHeaders(s,n=>r.has(n));if(!u)return{init:e};for(const[n,f]of Object.entries(u))r.set(n,f);const o=u[http_exchange_1.LENS_REQUEST_ID_HEADER];return{init:{...e,headers:r},...o?{calleeRequestId:o}:{}}}catch{return{init:e}}}E(stampFetch,"stampFetch"),d(stampFetch,"stampFetch");function stampRequest(t,e){try{if(t.headersSent||typeof t.setHeader!="function"||typeof t.getHeader!="function")return;const s=hopHeaders(e,i=>t.getHeader(i)!==void 0);if(!s)return;for(const[i,r]of Object.entries(s))t.setHeader(i,r);return s[http_exchange_1.LENS_REQUEST_ID_HEADER]}catch{return}}E(stampRequest,"stampRequest"),d(stampRequest,"stampRequest");const pendingExchanges=new Set;async function flushEgressCaptureForTests(){await Promise.allSettled([...pendingExchanges])}E(flushEgressCaptureForTests,"flushEgressCaptureForTests"),d(flushEgressCaptureForTests,"flushEgressCaptureForTests");async function finishExchange(t){const e=[],s=[],i=await(0,http_capture_1.requestPart)(t.request,t.url.search,t.mode,!!t.aiOpaque,s,e,!!t.ai&&!t.aiOpaque),r=t.reading?await t.reading.catch(()=>{}):void 0,{headers:u,redacted:o}=(0,http_store_1.redactHeaders)(t.responseHeaders??[]);e.push(...o);const n=typeof t.status=="number"?t.status:void 0,f={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.statusText?{statusText:t.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:i,response:{headers:u,...r?.body?{body:r.body}:{},...r?.stream?{stream:r.stream}:{}},redacted:{headers:[...new Set(e)],bodyFields:[...s.map(h=>`request.${h}`),...t.responseFields.map(h=>`response.${h}`)]},...t.ai?{ai:{provider:t.ai}}:{}};(0,http_store_1.writeHttpExchange)(f)}E(finishExchange,"finishExchange"),d(finishExchange,"finishExchange");function trackExchange(t){const e=(typeof t=="function"?new Promise(s=>setImmediate(s)).then(()=>finishExchange(t())):finishExchange(t)).catch(()=>{});pendingExchanges.add(e),e.finally(()=>pendingExchanges.delete(e))}E(trackExchange,"trackExchange"),d(trackExchange,"trackExchange");function tapEgress(){tapFetch(),tapHttpModule("http"),tapHttpModule("https")}E(tapEgress,"tapEgress"),d(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}E(resetEgressTapForTests,"resetEgressTapForTests"),d(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)}E(restoreHttp,"restoreHttp"),d(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)(),i=(0,ai_providers_1.parseExtraHosts)(process.env.LENSMCP_AI_HOSTS);t.fetch=(async(r,u)=>{let o="",n="/",f=!1,h;try{const p=new URL(typeof r=="string"?r:r instanceof URL?r.href:r.url);h=p,o=p.hostname,f=p.search.length>1,n=capturePath(p.pathname,p.search)}catch{}if(isInternal(o))return e(r,u);const g=Date.now(),c=(0,flow_context_1.flowContext)(),a=(0,http_exchange_1.isServiceHopHost)(o)?stampFetch(r,u,c):{init:u},x=a.init,m=a.calleeRequestId,_=(0,flow_context_1.liveFlowContext)();_&&(_.externalCallCount=(_.externalCallCount??0)+1);const T=typeof r=="object"&&r!==null&&!(r instanceof URL)?r.method:void 0,R=String(u?.method??T??"GET").toUpperCase(),q=s!=="off"&&h?(0,http_store_1.newExchangeId)():void 0,$=q&&h&&R==="POST"?(0,ai_providers_1.matchTarget)(o,h.port,h.pathname,i)?.provider:void 0,I=!!$&&(0,tap_ai_1.aiCaptureMode)()!=="content"&&!(0,capture_file_1.aiBodiesAsked)(),b=!!$&&!I,H=b&&!!h&&!/\/embeddings\/?$/.test(h.pathname),M=q?(0,http_capture_1.takeRequest)(r,x,s==="bodies",b):void 0,S=d(p=>({id:q,mode:s,method:R,url:h,path:h.pathname.length>256?`${h.pathname.slice(0,256)}\u2026`:h.pathname,startedAt:g,ctx:c,request:M,...$?{ai:$}:{},...I?{aiOpaque:I}:{},responseFields:[],...p}),"job");try{const p=await e(r,x),l=Date.now()-g,y=p.status>=500,w=[];let A;if(q&&s==="bodies")try{A=(0,http_capture_1.readResponse)(p,p.clone(),g,{ai:I,large:H,paths:w})}catch{A=void 0}const C=(0,emit_1.lensEmit)("external","info",`egress ${R} ${o} \u2192 ${p.status}`,{name:o,kind:"egress",method:R,host:o,path:n,status:p.status,...f?{hasQuery:!0}:{},startedAt:g,durationMs:l,...q?{exchangeId:q}:{},...m?{calleeRequestId:m}:{}},fingerprintFor(o,y),c);if(q&&C){const F={status:p.status,...p.statusText?{statusText:p.statusText}:{},responseHeaders:(0,http_capture_1.headerPairs)(p.headers)};trackExchange(S({...F,ttfbMs:l,responseFields:w,...A?{reading:A}:{}}))}return p}catch(p){const l=String(p?.message??p),y=(0,emit_1.lensEmit)("external","error",`egress ${R} ${o} failed`,{name:o,kind:"egress",method:R,host:o,path:n,...f?{hasQuery:!0}:{},error:l,startedAt:g,durationMs:Date.now()-g,...q?{exchangeId:q}:{},...m?{calleeRequestId:m}:{}},fingerprintFor(o,!0),c);throw q&&y&&trackExchange(S({error:l})),p}})}E(tapFetch,"tapFetch"),d(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 i=(0,http_store_1.httpCaptureMode)(),r=(0,ai_providers_1.parseExtraHosts)(process.env.LENSMCP_AI_HOSTS),u=d(function(...o){const n=s.apply(this,o);try{observe(t,o,n,i,r)}catch{}return n},"wrapped");u.__lensmcp=!0,e.request=u,typeof e.get=="function"&&(e.get=function(...o){const n=u.apply(this,o);return n.end(),n})}E(tapHttpModule,"tapHttpModule"),d(tapHttpModule,"tapHttpModule");const headerOf=d((t,e)=>{const s=t?.[e];return Array.isArray(s)?s.join(", "):s??""},"headerOf");function teeRequest(t,e,s,i=!1){const r=e?new http_capture_1.WrittenBody(!s,i):void 0,u=d(()=>{try{return t.getHeaders?.()}catch{return}},"sentHeaders");let o,n=!1;const f=t.write;r&&typeof f=="function"&&(t.write=function(g,...c){try{n||r.take(g,c[0])}catch{}return f.call(this,g,...c)});const h=t.end;return t.end=function(g,...c){try{n||(n=!0,r?.take(g,c[0]),o=u())}catch{}return h.call(this,g,...c)},()=>(0,http_capture_1.writtenRequest)(o??u(),r)}E(teeRequest,"teeRequest"),d(teeRequest,"teeRequest");function teeResponse(t,e,s){let i=!1;const r=d(n=>{if(i)return;i=!0,clearTimeout(u);const f=Date.now();s(()=>e.size?e.finish(n,f):{endedAt:f})},"finish"),u=setTimeout(()=>r(!0),http_capture_1.MAX_STREAM_MS);u.unref?.();const o=t.emit;t.emit=function(n,...f){try{return o.call(this,n,...f)}finally{try{if(n==="data"){const h=i?void 0:(0,http_capture_1.chunkBytes)(f[0],t.readableEncoding);h&&!e.push(h)&&r(!1)}else n==="end"?r(!1):(n==="aborted"||n==="error"||n==="close")&&r(!0)}catch{}}}}E(teeResponse,"teeResponse"),d(teeResponse,"teeResponse");function observe(t,e,s,i,r){const u=parseTarget(e,t),{host:o,method:n,path:f,hasQuery:h}=u;if(isInternal(o))return;const g=Date.now(),c=(0,flow_context_1.flowContext)(),a=(0,http_exchange_1.isServiceHopHost)(o)?stampRequest(s,c):void 0,x=(0,flow_context_1.liveFlowContext)();x&&(x.externalCallCount=(x.externalCallCount??0)+1);const m=i!=="off"?targetUrl(u):void 0,_=m?(0,http_store_1.newExchangeId)():void 0,T=_&&m&&n==="POST"?(0,ai_providers_1.matchTarget)(o,m.port,m.pathname,r)?.provider:void 0,R=!!T&&(0,tap_ai_1.aiCaptureMode)()!=="content"&&!(0,capture_file_1.aiBodiesAsked)(),q=!!T&&!R,$=q&&!!m&&!/\/embeddings\/?$/.test(m.pathname),I=_?teeRequest(s,i==="bodies",R,q):void 0,b=d(l=>({id:_,mode:i,method:n,url:m,path:clipPath(m.pathname),startedAt:g,ctx:c,request:I(),...T?{ai:T}:{},...R?{aiOpaque:R}:{},responseFields:[],...l}),"job");let H=!1;const M=d(l=>{if(H)return;H=!0;const y=l?.statusCode,w=Date.now()-g,A=(0,emit_1.lensEmit)("external","info",`egress ${n} ${o} \u2192 ${y??"?"}`,{name:o,kind:"egress",method:n,host:o,path:f,...y!=null?{status:y}:{},...h?{hasQuery:!0}:{},startedAt:g,durationMs:w,..._?{exchangeId:_}:{},...a?{calleeRequestId:a}:{}},fingerprintFor(o,y!=null&&y>=500),c);if(!_||!A||!l||typeof l.emit!="function")return;const C={...y!=null?{status:y}:{},...l.statusMessage?{statusText:l.statusMessage}:{},responseHeaders:(0,http_capture_1.headerPairs)(l.headers),ttfbMs:w};if(i!=="bodies"){trackExchange(()=>b(C));return}const F=[],P=new http_capture_1.ResponseSink(headerOf(l.headers,"content-type"),g,{ai:R,large:$,paths:F,encoding:headerOf(l.headers,"content-encoding")});teeResponse(l,P,D=>trackExchange(()=>b({...C,responseFields:F,reading:Promise.resolve(D())})))},"onResponse"),S=d(l=>{if(H)return;H=!0;const y=String(l?.message??l),w=(0,emit_1.lensEmit)("external","error",`egress ${n} ${o} failed`,{name:o,kind:"egress",method:n,host:o,path:f,...h?{hasQuery:!0}:{},error:y,startedAt:g,durationMs:Date.now()-g,..._?{exchangeId:_}:{},...a?{calleeRequestId:a}:{}},fingerprintFor(o,!0),c);_&&w&&trackExchange(()=>b({error:y}))},"onError"),p=s.emit;s.emit=function(l,...y){try{l==="response"?M(y[0]):l==="error"&&S(y[0])}catch{}return p.call(this,l,...y)}}E(observe,"observe"),d(observe,"observe");function targetUrl(t){try{const e=t.host.includes(":")&&!t.host.startsWith("[")?`[${t.host}]`:t.host,s=t.pathname.startsWith("/")?t.pathname:`/${t.pathname}`;return new URL(`${t.protocol}//${e}${t.port?`:${t.port}`:""}${s}${t.search}`)}catch{return}}E(targetUrl,"targetUrl"),d(targetUrl,"targetUrl");function parseTarget(t,e="http"){let s="",i="GET",r="/",u=!1,o=`${e}:`,n="",f="/",h="";const g=d((c,a)=>{u=a.length>1,r=capturePath(c,a),f=c||"/",h=a},"take");for(const c of t)if(typeof c=="string")try{const a=new URL(c);s=a.hostname,o=a.protocol,n=a.port,g(a.pathname,a.search)}catch{}else if(c instanceof URL)s=c.hostname,o=c.protocol,n=c.port,g(c.pathname,c.search);else if(c&&typeof c=="object"){const a=c;if(typeof a.method=="string"&&(i=a.method.toUpperCase()),typeof a.socketPath=="string")return{host:"",method:i,path:r,hasQuery:u,protocol:o,port:n,pathname:f,search:h};if(typeof a.path=="string"){const x=a.path,m=x.indexOf("?");g(m===-1?x:x.slice(0,m),m===-1?"":x.slice(m))}if(typeof a.protocol=="string"&&a.protocol&&(o=a.protocol),(typeof a.port=="number"||typeof a.port=="string"&&a.port)&&(n=String(a.port)),typeof a.hostname=="string"&&a.hostname)s=a.hostname;else if(typeof a.host=="string"&&a.host){const[x,m]=a.host.split(":");s=x??"",m&&!(typeof a.port=="number"||typeof a.port=="string")&&(n=m)}}return{host:s,method:i,path:r,hasQuery:u,protocol:o,port:n,pathname:f,search:h}}E(parseTarget,"parseTarget"),d(parseTarget,"parseTarget");
|
package/lib/tap-ingress.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var A=Object.defineProperty;var c=(r,e)=>A(r,"name",{value:e,configurable:!0});var x=Object.defineProperty,a=c((r,e)=>x(r,"name",{value:e,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.EXCHANGE_ID=void 0,exports.tapIngress=tapIngress,exports.resetIngressTapForTests=resetIngressTapForTests;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),zlib=tslib_1.__importStar(require("node:zlib")),headless_flow_1=require("@lensmcp/core/headless-flow"),http_exchange_1=require("@lensmcp/core/http-exchange"),emit_1=require("./emit"),http_store_1=require("./http-store");exports.EXCHANGE_ID=Symbol.for("lensmcp.exchangeId");const MAX_READ=2*1024*1024,MAX_PATH_CHARS=256;let originalEmit;function tapIngress(){if(originalEmit)return;const r=(0,http_store_1.httpCaptureMode)();if(r==="off")return;const e=http.Server.prototype,s=e.emit;originalEmit=s,e.emit=function(t,...i){if(t==="request")try{watch(i[0],i[1],r)}catch{}return s.call(this,t,...i)}}c(tapIngress,"tapIngress"),a(tapIngress,"tapIngress");function resetIngressTapForTests(){originalEmit&&(http.Server.prototype.emit=originalEmit),originalEmit=void 0}c(resetIngressTapForTests,"resetIngressTapForTests"),a(resetIngressTapForTests,"resetIngressTapForTests");const one=a(r=>r===void 0?void 0:Array.isArray(r)?r.join(", "):String(r),"one"),pairs=a(r=>Object.entries(r).flatMap(([e,s])=>s===void 0?[]:[[e,one(s)]]),"pairs");class Tee{static{c(this,"Tee")}static{a(this,"Tee")}constructor(){this.chunks=[],this.read=0,this.size=0}take(e,s){if(e==null||typeof e=="function")return;const t=Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e):typeof e=="string"?Buffer.from(e,typeof s=="string"?s:"utf8"):void 0;t&&(this.size+=t.length,this.read<MAX_READ&&(this.chunks.push(t),this.read+=t.length))}bytes(){return Buffer.concat(this.chunks)}}function watch(r,e,s){if(!r||!e||r[exports.EXCHANGE_ID])return;const t=r.url??"/",i=t.indexOf("?"),l=i<0?t:t.slice(0,i);if((0,headless_flow_1.isProbePath)(l))return;const h=(0,http_store_1.newExchangeId)();r[exports.EXCHANGE_ID]=h;const y=Date.now(),f=s==="bodies",g=new Tee,d=new Tee;if(f){const m=r.emit;r.emit=function(o,...n){return o==="data"&&g.take(n[0]),m.call(this,o,...n)};const w=e.write,b=e.end;e.write=function(o,...n){return d.take(o,n[0]),w.call(this,o,...n)},e.end=function(o,...n){return d.take(o,n[0]),b.call(this,o,...n)}}let u=!1;const p=a(()=>{if(!u){u=!0;try{(0,http_store_1.writeHttpExchange)(exchangeOf(h,r,e,{url:t,q:i,path:l,startedAt:y,bodies:f,sent:g,answered:d}))}catch{}}},"finish");e.once("finish",p),e.once("close",p)}c(watch,"watch"),a(watch,"watch");function unpacked(r,e){try{if(r==="gzip"||r==="x-gzip")return zlib.gunzipSync(e,{maxOutputLength:MAX_READ});if(r==="br")return zlib.brotliDecompressSync(e,{maxOutputLength:MAX_READ});if(r==="deflate")return zlib.inflateSync(e,{maxOutputLength:MAX_READ})}catch{return}}c(unpacked,"unpacked"),a(unpacked,"unpacked");function exchangeOf(r,e,s,t){const i=[],l=[],h=[],y=(0,http_store_1.redactHeaders)(pairs(e.headers));i.push(...y.redacted);const f=(0,http_store_1.redactHeaders)(pairs(s.getHeaders()));i.push(...f.redacted);const g=t.bodies&&t.q>=0?(0,http_store_1.redactQuery)(t.url.slice(t.q)):[];let d;t.bodies&&t.sent.size&&(d=(0,http_store_1.keepBody)(one(e.headers["content-type"])??"",t.sent.bytes(),t.sent.size,l));let u;if(t.bodies&&t.answered.size){const n=one(s.getHeader("content-type"))??"",_=(one(s.getHeader("content-encoding"))??"").trim().toLowerCase();if(!_||_==="identity")u=(0,http_store_1.keepBody)(n,t.answered.bytes(),t.answered.size,h);else{const E=t.answered.size<=MAX_READ?unpacked(_,t.answered.bytes()):void 0;u=E?(0,http_store_1.keepBody)(n,E,E.length,h):{contentType:n.split(";")[0].trim().toLowerCase(),size:t.answered.size,kept:0,truncated:!1,omitted:"encoded"}}}const p=one(e.headers.host)??"localhost",m=t.path.length>MAX_PATH_CHARS?`${t.path.slice(0,MAX_PATH_CHARS)}\u2026`:t.path,w=one(e.headers["x-lensmcp-flow-id"]),b=one(e.headers[http_exchange_1.LENS_REQUEST_ID_HEADER])||one(e.headers["x-request-id"]),o=!s.writableFinished;return{schemaVersion:1,id:r,capturedAt:Date.now(),side:"ingress",service:emit_1.PROJECT,...w?{flowId:w}:{},...b?{requestId:b}:{},method:(e.method??"GET").toUpperCase(),url:`http://${p}${m}`,host:p.split(":")[0]??p,path:m,status:s.statusCode,...s.statusMessage?{statusText:s.statusMessage}:{},...o?{error:"the client went away before the answer ended"}:{},startedAt:t.startedAt,durationMs:Date.now()-t.startedAt,capture:t.bodies?"bodies":"headers",request:{headers:y.headers,...g.length?{query:g}:{},...d?{body:d}:{}},response:{headers:f.headers,...u?{body:u}:{}},redacted:{headers:[...new Set(i)],bodyFields:[...l.map(n=>`request.${n}`),...h.map(n=>`response.${n}`)]}}}c(exchangeOf,"exchangeOf"),a(exchangeOf,"exchangeOf");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/node-instrumentation",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.32",
|
|
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.25.
|
|
19
|
-
"@lensmcp/nest-instrumentation": "1.25.
|
|
20
|
-
"@lensmcp/protocol-types": "1.25.
|
|
18
|
+
"@lensmcp/core": "1.25.32",
|
|
19
|
+
"@lensmcp/nest-instrumentation": "1.25.32",
|
|
20
|
+
"@lensmcp/protocol-types": "1.25.32",
|
|
21
21
|
"tslib": "^2.3.0"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|