@lensmcp/node-instrumentation 1.23.3 → 1.23.6
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/ai-providers.d.ts +85 -0
- package/lib/ai-providers.js +16 -0
- package/lib/ai-store.d.ts +20 -0
- package/lib/ai-store.js +1 -0
- package/lib/emit.d.ts +6 -1
- package/lib/emit.js +2 -2
- package/lib/install.js +1 -1
- package/lib/tap-ai.d.ts +7 -0
- package/lib/tap-ai.js +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an AI HTTP call IS — pure, I/O-free, so every shape is unit-testable without a network.
|
|
3
|
+
*
|
|
4
|
+
* Three wire formats cover the providers worth recognising out of the box:
|
|
5
|
+
* - OpenAI Chat Completions POST …/chat/completions (also Azure OpenAI, Ollama's /v1, vLLM, …)
|
|
6
|
+
* - OpenAI Responses POST …/responses
|
|
7
|
+
* - Anthropic Messages POST …/messages
|
|
8
|
+
* plus OpenAI-style embeddings, for which only METADATA is ever kept (a vector is not a conversation).
|
|
9
|
+
*
|
|
10
|
+
* Anything that speaks one of those formats can be added with `LENSMCP_AI_HOSTS` — the matcher keys on
|
|
11
|
+
* host + path, never on a vendor SDK, so a stub on `*.stub.test` and a local Ollama are first-class.
|
|
12
|
+
*
|
|
13
|
+
* Everything here is defensive about shape: a provider adding a field, or a proxy returning an error
|
|
14
|
+
* body where a completion was expected, must degrade to "less detail", never throw.
|
|
15
|
+
*/
|
|
16
|
+
import type { AiMessageContent, AiToolCallContent, AiUsageContent } from '@lensmcp/core/ai-content';
|
|
17
|
+
export type AiProvider = 'openai' | 'anthropic';
|
|
18
|
+
export type AiApi = 'chat' | 'responses' | 'embeddings' | 'messages';
|
|
19
|
+
export interface AiTarget {
|
|
20
|
+
provider: AiProvider;
|
|
21
|
+
api: AiApi;
|
|
22
|
+
}
|
|
23
|
+
export interface NormalizedRequest {
|
|
24
|
+
model?: string;
|
|
25
|
+
system?: string;
|
|
26
|
+
messages: AiMessageContent[];
|
|
27
|
+
tools: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
}>;
|
|
31
|
+
params: Record<string, string | number | boolean>;
|
|
32
|
+
stream: boolean;
|
|
33
|
+
/** Embeddings only: how many inputs there were. The inputs themselves are never kept. */
|
|
34
|
+
inputCount?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface NormalizedResponse {
|
|
37
|
+
id?: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
completion: string;
|
|
40
|
+
reasoning?: string;
|
|
41
|
+
toolCalls: AiToolCallContent[];
|
|
42
|
+
finishReason?: string;
|
|
43
|
+
usage: AiUsageContent;
|
|
44
|
+
error?: string;
|
|
45
|
+
/** Embeddings only: how many vectors came back. */
|
|
46
|
+
vectorCount?: number;
|
|
47
|
+
}
|
|
48
|
+
/** `host` or `host:port` → provider. Built from `LENSMCP_AI_HOSTS`, e.g. `localhost:11434=openai`. */
|
|
49
|
+
export type ExtraHosts = Map<string, AiProvider>;
|
|
50
|
+
export declare function parseExtraHosts(spec: string | undefined): ExtraHosts;
|
|
51
|
+
/**
|
|
52
|
+
* Is this request an AI call we know how to read? Both the host AND the path have to say so — a
|
|
53
|
+
* `GET /v1/models` to api.openai.com, or `/v1/messages/batches`, is traffic, not a conversation.
|
|
54
|
+
*/
|
|
55
|
+
export declare function matchTarget(host: string, port: string, pathname: string, extra: ExtraHosts): AiTarget | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* A message body — a string, or a list of typed parts — as readable text. Binary parts become a marker
|
|
58
|
+
* (an inlined base64 image would be megabytes of noise and tell a reader nothing).
|
|
59
|
+
*/
|
|
60
|
+
export declare function flattenContent(content: unknown): string;
|
|
61
|
+
/** The request body as the conversation it carried. Never throws; an unrecognised body is "no messages". */
|
|
62
|
+
export declare function normalizeRequest(target: AiTarget, body: unknown): NormalizedRequest;
|
|
63
|
+
/**
|
|
64
|
+
* Token accounting under any of the three vocabularies; absent fields stay absent.
|
|
65
|
+
*
|
|
66
|
+
* `derive` fills `totalTokens` from prompt + completion when the provider did not send one. A stream
|
|
67
|
+
* must pass `false` while it merges partial usage (Anthropic reports input tokens at `message_start`
|
|
68
|
+
* and the final output count at `message_delta`) — a total derived from the first half would otherwise
|
|
69
|
+
* survive the merge and be wrong; the accumulator derives once, at the end.
|
|
70
|
+
*/
|
|
71
|
+
export declare function mapUsage(raw: unknown, derive?: boolean): AiUsageContent;
|
|
72
|
+
/**
|
|
73
|
+
* A non-streaming response body. A 4xx/5xx body is `{ error: … }` in both vocabularies, and the
|
|
74
|
+
* message is the single most useful thing to keep, so it is read whatever the status was.
|
|
75
|
+
*/
|
|
76
|
+
export declare function normalizeResponse(target: AiTarget, body: unknown): NormalizedResponse;
|
|
77
|
+
export interface StreamAccumulator {
|
|
78
|
+
/** Feed decoded text as it arrives; partial events are buffered until their terminating blank line. */
|
|
79
|
+
push(chunk: string): void;
|
|
80
|
+
/** Flush a trailing unterminated event and return everything seen. Safe to call once. */
|
|
81
|
+
end(): NormalizedResponse;
|
|
82
|
+
/** How many events have been parsed — lets the tap time the first one. */
|
|
83
|
+
readonly eventCount: number;
|
|
84
|
+
}
|
|
85
|
+
export declare function createStreamAccumulator(target: AiTarget): StreamAccumulator;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";var j=Object.defineProperty;var d=(t,e)=>j(t,"name",{value:e,configurable:!0});var M=Object.defineProperty,u=d((t,e)=>M(t,"name",{value:e,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseExtraHosts=parseExtraHosts,exports.matchTarget=matchTarget,exports.flattenContent=flattenContent,exports.normalizeRequest=normalizeRequest,exports.mapUsage=mapUsage,exports.normalizeResponse=normalizeResponse,exports.createStreamAccumulator=createStreamAccumulator;const isObj=u(t=>typeof t=="object"&&t!==null&&!Array.isArray(t),"isObj"),asArr=u(t=>Array.isArray(t)?t:[],"asArr"),asStr=u(t=>typeof t=="string"?t:void 0,"asStr"),asNum=u(t=>typeof t=="number"&&Number.isFinite(t)?t:void 0,"asNum"),MAX_ACCUMULATED_CHARS=512*1024,MAX_EVENT_BUFFER_CHARS=1024*1024,MAX_BLOCK_JSON_CHARS=2e3,KNOWN_HOSTS=[{test:/^api\.openai\.com$/i,provider:"openai"},{test:/\.openai\.azure\.com$/i,provider:"openai"},{test:/^api\.anthropic\.com$/i,provider:"anthropic"}],authorityOf=u(t=>t.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//,"").replace(/[/?#].*$/,""),"authorityOf");function parseExtraHosts(t){const e=new Map;for(const o of(t??"").split(",")){const a=o.lastIndexOf("=");if(a===-1)continue;const n=authorityOf(o.slice(0,a)),r=o.slice(a+1).trim().toLowerCase();n&&(r==="openai"||r==="anthropic")&&e.set(n,r)}return e}d(parseExtraHosts,"parseExtraHosts"),u(parseExtraHosts,"parseExtraHosts");function apiFor(t,e){const o=e.replace(/\/+$/,"").toLowerCase();if(t==="anthropic")return o.endsWith("/messages")?"messages":void 0;if(o.endsWith("/chat/completions"))return"chat";if(o.endsWith("/responses"))return"responses";if(o.endsWith("/embeddings"))return"embeddings"}d(apiFor,"apiFor"),u(apiFor,"apiFor");function matchTarget(t,e,o,a){const n=t.toLowerCase(),r=a.get(e?`${n}:${e}`:n)??a.get(n)??KNOWN_HOSTS.find(p=>p.test.test(n))?.provider;if(!r)return;const c=apiFor(r,o);return c?{provider:r,api:c}:void 0}d(matchTarget,"matchTarget"),u(matchTarget,"matchTarget");function jsonText(t,e=MAX_BLOCK_JSON_CHARS){try{const o=JSON.stringify(t);return o===void 0?"":o.length>e?`${o.slice(0,e)}\u2026`:o}catch{return""}}d(jsonText,"jsonText"),u(jsonText,"jsonText");const argsText=u(t=>typeof t=="string"?t:t==null?void 0:jsonText(t,8192),"argsText");function flattenContent(t){if(t==null)return"";if(typeof t=="string")return t;if(!Array.isArray(t))return jsonText(t);const e=[];for(const o of t){if(typeof o=="string"){e.push(o);continue}if(!isObj(o))continue;const a=asStr(o.type)??"";switch(a){case"text":case"input_text":case"output_text":e.push(asStr(o.text)??"");break;case"refusal":e.push(`[refusal] ${asStr(o.refusal)??""}`);break;case"image_url":case"image":case"input_image":e.push("[image]");break;case"input_audio":case"audio":e.push("[audio]");break;case"file":case"input_file":case"document":e.push("[file]");break;case"tool_use":e.push(`[tool_use ${asStr(o.name)??""} ${jsonText(o.input)}]`);break;case"tool_result":e.push(`[tool_result ${flattenContent(o.content)}]`);break;case"thinking":e.push(`[thinking] ${asStr(o.thinking)??""}`);break;case"redacted_thinking":e.push("[redacted_thinking]");break;default:e.push(asStr(o.text)??`[${a||"part"}]`)}}return e.filter(o=>o!=="").join(`
|
|
2
|
+
`)}d(flattenContent,"flattenContent"),u(flattenContent,"flattenContent");const SCALAR_PARAMS={chat:["temperature","top_p","max_tokens","max_completion_tokens","n","seed","presence_penalty","frequency_penalty","tool_choice","reasoning_effort","service_tier"],responses:["temperature","top_p","max_output_tokens","tool_choice","parallel_tool_calls","service_tier"],messages:["max_tokens","temperature","top_p","top_k"],embeddings:["dimensions","encoding_format"]};function scalarParams(t,e){const o={};for(const p of SCALAR_PARAMS[e]){const _=t[p];(typeof _=="string"||typeof _=="number"||typeof _=="boolean")&&(o[p]=_)}const a=t.reasoning;isObj(a)&&typeof a.effort=="string"&&(o.reasoning_effort=a.effort);const n=t.thinking;isObj(n)&&(typeof n.type=="string"&&(o.thinking=n.type),typeof n.budget_tokens=="number"&&(o.thinking_budget_tokens=n.budget_tokens));const r=t.response_format;isObj(r)&&typeof r.type=="string"&&(o.response_format=r.type);const c=t.tool_choice;return isObj(c)&&typeof c.type=="string"&&(o.tool_choice=c.type),o}d(scalarParams,"scalarParams"),u(scalarParams,"scalarParams");function toolsOf(t){const e=[];for(const o of asArr(t.tools)){if(!isObj(o))continue;const a=isObj(o.function)?o.function:void 0,n=asStr(a?.name)??asStr(o.name)??asStr(o.type);if(!n)continue;const r=asStr(a?.description)??asStr(o.description);e.push(r?{name:n,description:r}:{name:n})}return e}d(toolsOf,"toolsOf"),u(toolsOf,"toolsOf");function chatMessage(t){let e=flattenContent(t.content);for(const o of asArr(t.tool_calls)){if(!isObj(o))continue;const a=isObj(o.function)?o.function:{};e+=`${e?`
|
|
3
|
+
`:""}[tool_call ${asStr(a.name)??""} ${asStr(a.arguments)??""}]`}return{role:asStr(t.role)??"unknown",content:e}}d(chatMessage,"chatMessage"),u(chatMessage,"chatMessage");function responsesInput(t){if(typeof t=="string")return[{role:"user",content:t}];const e=[];for(const o of asArr(t)){if(!isObj(o))continue;const a=asStr(o.type);a==="function_call_output"?e.push({role:"tool",content:flattenContent(o.output)}):a==="function_call"?e.push({role:"assistant",content:`[tool_call ${asStr(o.name)??""} ${asStr(o.arguments)??""}]`}):e.push({role:asStr(o.role)??a??"user",content:flattenContent(o.content)})}return e}d(responsesInput,"responsesInput"),u(responsesInput,"responsesInput");function normalizeRequest(t,e){const o={messages:[],tools:[],params:{},stream:!1};if(!isObj(e))return o;const a={...o,...asStr(e.model)?{model:asStr(e.model)}:{},stream:e.stream===!0,params:scalarParams(e,t.api),tools:toolsOf(e)};switch(t.api){case"chat":a.messages=asArr(e.messages).filter(isObj).map(chatMessage);break;case"responses":{const n=asStr(e.instructions);n&&(a.system=n),a.messages=responsesInput(e.input);break}case"messages":{const n=e.system,r=typeof n=="string"?n:flattenContent(n);r&&(a.system=r),a.messages=asArr(e.messages).filter(isObj).map(c=>({role:asStr(c.role)??"unknown",content:flattenContent(c.content)}));break}case"embeddings":{const n=e.input;a.inputCount=Array.isArray(n)?n.length:n===void 0?0:1;break}}return a}d(normalizeRequest,"normalizeRequest"),u(normalizeRequest,"normalizeRequest");function mapUsage(t,e=!0){if(!isObj(t))return{};const o=isObj(t.prompt_tokens_details)?t.prompt_tokens_details:isObj(t.input_tokens_details)?t.input_tokens_details:{},a=isObj(t.completion_tokens_details)?t.completion_tokens_details:isObj(t.output_tokens_details)?t.output_tokens_details:{},n={},r=asNum(t.prompt_tokens)??asNum(t.input_tokens),c=asNum(t.completion_tokens)??asNum(t.output_tokens),p=asNum(o.cached_tokens)??asNum(t.cache_read_input_tokens),_=asNum(a.reasoning_tokens);r!==void 0&&(n.promptTokens=r),c!==void 0&&(n.completionTokens=c),p!==void 0&&(n.cachedTokens=p),_!==void 0&&(n.reasoningTokens=_);const f=asNum(t.total_tokens);return f!==void 0?n.totalTokens=f:e&&r!==void 0&&c!==void 0&&(n.totalTokens=r+c),n}d(mapUsage,"mapUsage"),u(mapUsage,"mapUsage");const withTotal=u(t=>t.totalTokens===void 0&&t.promptTokens!==void 0&&t.completionTokens!==void 0?{...t,totalTokens:t.promptTokens+t.completionTokens}:t,"withTotal");function errorMessage(t){const e=t.error;if(typeof e=="string")return e;if(isObj(e))return asStr(e.message)??asStr(e.type)??asStr(e.code)}d(errorMessage,"errorMessage"),u(errorMessage,"errorMessage");function normalizeResponse(t,e){const o={completion:"",toolCalls:[],usage:{}};if(!isObj(e))return o;const a=asStr(e.id),n=asStr(e.model);a&&(o.id=a),n&&(o.model=n);const r=errorMessage(e);switch(r&&(o.error=r),o.usage=mapUsage(e.usage),t.api){case"chat":{const c=asArr(e.choices).find(isObj),p=c&&isObj(c.message)?c.message:void 0;if(p){o.completion=flattenContent(p.content);const f=asStr(p.refusal);f&&(o.completion+=`${o.completion?`
|
|
4
|
+
`:""}[refusal] ${f}`);const h=asStr(p.reasoning_content)??asStr(p.reasoning);h&&(o.reasoning=h);for(const k of asArr(p.tool_calls)){if(!isObj(k))continue;const y=isObj(k.function)?k.function:{},b=argsText(y.arguments);o.toolCalls.push({...asStr(k.id)?{id:asStr(k.id)}:{},name:asStr(y.name)??"tool",...b!==void 0?{arguments:b}:{}})}const m=isObj(p.function_call)?p.function_call:void 0;if(m){const k=argsText(m.arguments);o.toolCalls.push({name:asStr(m.name)??"tool",...k!==void 0?{arguments:k}:{}})}}const _=asStr(c?.finish_reason);_&&(o.finishReason=_);break}case"responses":{const c=[],p=[];for(const m of asArr(e.output)){if(!isObj(m))continue;const k=asStr(m.type);if(k==="message")c.push(flattenContent(m.content));else if(k==="function_call")o.toolCalls.push({...asStr(m.call_id)??asStr(m.id)?{id:asStr(m.call_id)??asStr(m.id)}:{},name:asStr(m.name)??"tool",...asStr(m.arguments)!==void 0?{arguments:asStr(m.arguments)}:{}});else if(k==="reasoning")for(const y of asArr(m.summary))isObj(y)&&asStr(y.text)&&p.push(asStr(y.text))}o.completion=c.filter(Boolean).join(`
|
|
5
|
+
`),p.length&&(o.reasoning=p.join(`
|
|
6
|
+
`));const _=isObj(e.incomplete_details)?asStr(e.incomplete_details.reason):void 0,f=asStr(e.status),h=_??f;h&&(o.finishReason=h),!o.error&&isObj(e.error)&&(o.error=errorMessage(e));break}case"messages":{const c=[],p=[];for(const f of asArr(e.content)){if(!isObj(f))continue;const h=asStr(f.type);h==="text"?c.push(asStr(f.text)??""):h==="thinking"?p.push(asStr(f.thinking)??""):(h==="tool_use"||h==="server_tool_use"||h==="mcp_tool_use")&&o.toolCalls.push({...asStr(f.id)?{id:asStr(f.id)}:{},name:asStr(f.name)??"tool",arguments:jsonText(f.input)})}o.completion=c.filter(Boolean).join(`
|
|
7
|
+
`),p.length&&(o.reasoning=p.filter(Boolean).join(`
|
|
8
|
+
`));const _=asStr(e.stop_reason);_&&(o.finishReason=_);break}case"embeddings":o.vectorCount=asArr(e.data).length;break}return o}d(normalizeResponse,"normalizeResponse"),u(normalizeResponse,"normalizeResponse");function createStreamAccumulator(t){let e="",o=0,a,n,r="",c="",p="",_,f={},h,m;const k=new Map,y=u((i,s)=>{s&&(i==="text"?r.length<MAX_ACCUMULATED_CHARS&&(r+=s):i==="reasoning"?c.length<MAX_ACCUMULATED_CHARS&&(c+=s):p.length<MAX_ACCUMULATED_CHARS&&(p+=s))},"append"),b=u(i=>{let s=k.get(i);return s||(s={name:"",args:""},k.set(i,s)),s},"tool");function $(i){const s=asArr(i.choices).find(isObj),g=s&&isObj(s.delta)?s.delta:void 0;if(g){y("text",asStr(g.content)??""),y("refusal",asStr(g.refusal)??""),y("reasoning",asStr(g.reasoning_content)??asStr(g.reasoning)??"");const x=isObj(g.function_call)?g.function_call:void 0;if(x){const v=b(-1);asStr(x.name)&&(v.name+=asStr(x.name)),asStr(x.arguments)&&(v.args+=asStr(x.arguments))}for(const v of asArr(g.tool_calls)){if(!isObj(v))continue;const T=b(asNum(v.index)??0);asStr(v.id)&&(T.id=asStr(v.id));const C=isObj(v.function)?v.function:{};asStr(C.name)&&(T.name+=asStr(C.name)),asStr(C.arguments)&&(T.args+=asStr(C.arguments))}}const l=asStr(s?.finish_reason);l&&(_=l),isObj(i.usage)&&(f={...f,...mapUsage(i.usage,!1)})}d($,"R"),u($,"onChat");function O(i){const s=asStr(i.type)??"";s==="response.output_text.delta"?y("text",asStr(i.delta)??""):s==="response.completed"||s==="response.incomplete"||s==="response.failed"?m=normalizeResponse(t,i.response):s==="error"&&(h=asStr(i.message)??h)}d(O,"O"),u(O,"onResponses");function R(i){const s=asStr(i.type)??"",g=asNum(i.index)??0;if(s==="message_start"){const l=isObj(i.message)?i.message:{};a=asStr(l.id)??a,n=asStr(l.model)??n,f={...f,...mapUsage(l.usage,!1)}}else if(s==="content_block_start"){const l=isObj(i.content_block)?i.content_block:{};if(asStr(l.type)==="tool_use"||asStr(l.type)==="server_tool_use"||asStr(l.type)==="mcp_tool_use"){const x=b(g);x.id=asStr(l.id),x.name=asStr(l.name)??"tool"}else asStr(l.type)==="text"&&y("text",asStr(l.text)??"")}else if(s==="content_block_delta"){const l=isObj(i.delta)?i.delta:{},x=asStr(l.type);x==="text_delta"?y("text",asStr(l.text)??""):x==="thinking_delta"?y("reasoning",asStr(l.thinking)??""):x==="input_json_delta"&&(b(g).args+=asStr(l.partial_json)??"")}else if(s==="message_delta"){const l=isObj(i.delta)?i.delta:{};asStr(l.stop_reason)&&(_=asStr(l.stop_reason)),f={...f,...mapUsage(i.usage,!1)}}else s==="error"&&(h=errorMessage(i)??h)}d(R,"S"),u(R,"onAnthropic");function S(i){o+=1,a=asStr(i.id)??a,n=asStr(i.model)??n,t.api==="messages"?R(i):t.api==="responses"?O(i):$(i);const s=t.api==="messages"?void 0:errorMessage(i);s&&(h=s)}d(S,"b"),u(S,"onEvent");function A(i){const s=[];for(const l of i.split(`
|
|
9
|
+
`))l.startsWith("data:")&&s.push(l.slice(5).replace(/^ /,""));if(!s.length)return;const g=s.join(`
|
|
10
|
+
`);if(g!=="[DONE]")try{const l=JSON.parse(g);isObj(l)&&S(l)}catch{}}return d(A,"$"),u(A,"parseEvent"),{push(i){e=(e+i).replace(/\r\n/g,`
|
|
11
|
+
`);let s=e.indexOf(`
|
|
12
|
+
|
|
13
|
+
`);for(;s!==-1;)A(e.slice(0,s)),e=e.slice(s+2),s=e.indexOf(`
|
|
14
|
+
|
|
15
|
+
`);e.length>MAX_EVENT_BUFFER_CHARS&&(e="")},end(){e.trim()&&A(e),e="";const i=[...k.entries()].sort((g,l)=>g[0]-l[0]).map(([,g])=>({...g.id?{id:g.id}:{},name:g.name||"tool",...g.args?{arguments:g.args}:{}})),s={...a?{id:a}:{},...n?{model:n}:{},completion:p?`${r}${r?`
|
|
16
|
+
`:""}[refusal] ${p}`:r,...c?{reasoning:c}:{},toolCalls:i,..._?{finishReason:_}:{},usage:withTotal(f),...h?{error:h}:{}};return m?{...s,...m,completion:m.completion||s.completion,toolCalls:m.toolCalls.length?m.toolCalls:s.toolCalls,usage:{...s.usage,...m.usage}}:s},get eventCount(){return o}}}d(createStreamAccumulator,"createStreamAccumulator"),u(createStreamAccumulator,"createStreamAccumulator");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type AiCallContent } from '@lensmcp/core/ai-content';
|
|
2
|
+
export declare function redactAiText(text: string): string;
|
|
3
|
+
/** Time-ordered so a lexical sort is a chronological one; matches `isAiCallId`. */
|
|
4
|
+
export declare function newAiCallId(): string;
|
|
5
|
+
/** Shrink until the serialised record fits the per-call budget. Never returns something over it. */
|
|
6
|
+
export declare function fitAiContent(src: AiCallContent): {
|
|
7
|
+
json: string;
|
|
8
|
+
content: AiCallContent;
|
|
9
|
+
};
|
|
10
|
+
/** Delete one call's file — used when its event was dropped, so nothing is left pointing nowhere. */
|
|
11
|
+
export declare function removeAiContent(id: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* Persist one call. Best-effort: returns `undefined` on any failure so the tap can emit the event
|
|
14
|
+
* without a `contentRef` rather than lose the metadata. Written to a temp name and renamed, so a
|
|
15
|
+
* reader never sees half a file.
|
|
16
|
+
*/
|
|
17
|
+
export declare function writeAiContent(src: AiCallContent): {
|
|
18
|
+
id: string;
|
|
19
|
+
truncated: boolean;
|
|
20
|
+
} | undefined;
|
package/lib/ai-store.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var p=Object.defineProperty;var u=(e,n)=>p(e,"name",{value:n,configurable:!0});var f=Object.defineProperty,i=u((e,n)=>f(e,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.redactAiText=redactAiText,exports.newAiCallId=newAiCallId,exports.fitAiContent=fitAiContent,exports.removeAiContent=removeAiContent,exports.writeAiContent=writeAiContent;const tslib_1=require("tslib"),nodeFs=tslib_1.__importStar(require("node:fs")),ai_content_1=require("@lensmcp/core/ai-content"),redact_1=require("@lensmcp/core/redact"),emit_1=require("./emit"),mkdirSync=nodeFs.mkdirSync,writeFileSync=nodeFs.writeFileSync,renameSync=nodeFs.renameSync,readdirSync=nodeFs.readdirSync,unlinkSync=nodeFs.unlinkSync,AI_SECRET_PATTERNS=[/\bsk-(?:ant-|proj-|live-|test-)?[A-Za-z0-9_-]{20,}/g,/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,/\bgh[pousr]_[A-Za-z0-9]{30,}/g,/\bxox[abprs]-[A-Za-z0-9-]{10,}/g,/\bAIza[0-9A-Za-z_-]{35}\b/g],PEM_BEGIN="-----BEGIN ",PEM_UNTERMINATED_BODY_CHARS=4096;function redactPrivateKeys(e){if(!e.includes("PRIVATE KEY-----"))return e;let n="",t=0,r=!1;for(;;){const o=e.indexOf(PEM_BEGIN,t);if(o===-1)break;const s=e.indexOf("-----",o+PEM_BEGIN.length);if(s===-1)break;const d=e.slice(o+PEM_BEGIN.length,s);if(!/^[A-Z ]{0,30}PRIVATE KEY$/.test(d)){n+=e.slice(t,s+5),t=s+5;continue}n+=e.slice(t,o);const c=r?-1:e.indexOf("-----END ",s+5);let l;if(c===-1)r=!0,l=Math.min(e.length,s+5+PEM_UNTERMINATED_BODY_CHARS);else{const a=e.indexOf("-----",c+9);l=a===-1?e.length:a+5}n+=redact_1.REDACTED,t=l}return n+e.slice(t)}u(redactPrivateKeys,"redactPrivateKeys"),i(redactPrivateKeys,"redactPrivateKeys");const JSON_PAIR_RE=/"([A-Za-z0-9_.-]{1,64})"\s*:\s*"((?:[^"\\]|\\.){1,8192})"/g,ASSIGNMENT_RE=/\b([A-Za-z][A-Za-z0-9_-]{1,40})(\s*[:=]\s*)([^\s"'`,;&)}\]]{8,200})/g,CREDENTIAL_NAME_RE=/(token|secret|pass(?:word|wd)?|pwd|api[-_]?key|apikey|credential|authorization|bearer)/i;function redactStructured(e){return e.replace(JSON_PAIR_RE,(n,t)=>(0,redact_1.isSecretParam)(t)?`"${t}": "${redact_1.REDACTED}"`:n).replace(ASSIGNMENT_RE,(n,t,r)=>CREDENTIAL_NAME_RE.test(t)&&(0,redact_1.isSecretParam)(t)?`${t}${r}${redact_1.REDACTED}`:n)}u(redactStructured,"redactStructured"),i(redactStructured,"redactStructured");function redactAiText(e){let n=redactStructured((0,redact_1.redactText)(redactPrivateKeys(e)));for(const t of AI_SECRET_PATTERNS)n=n.replace(t,redact_1.REDACTED);return n}u(redactAiText,"redactAiText"),i(redactAiText,"redactAiText");const intEnv=i((e,n,t,r)=>{const o=Number(process.env[e]);return Number.isFinite(o)&&o>=t&&o<=r?Math.floor(o):n},"intEnv"),maxFieldChars=i(()=>intEnv("LENSMCP_AI_MAX_CHARS",32*1024,256,4*1024*1024),"maxFieldChars"),maxFileBytes=i(()=>intEnv("LENSMCP_AI_MAX_BYTES",256*1024,4*1024,16*1024*1024),"maxFileBytes"),maxFiles=i(()=>intEnv("LENSMCP_AI_MAX_FILES",200,1,1e4),"maxFiles");function newAiCallId(){const e=Date.now().toString(36).padStart(9,"0"),n=Math.random().toString(36).slice(2,8).padEnd(6,"0");return`ai_${e}${n}`}u(newAiCallId,"newAiCallId"),i(newAiCallId,"newAiCallId");const PRE_CLIP_FACTOR=4,MAX_MESSAGES=400;function sanitize(e,n){const t={cut:e.truncated===!0},r=i((s,d=n)=>{if(s===void 0)return;const c=d*PRE_CLIP_FACTOR;let l=s;return l.length>c&&(t.cut=!0,l=l.slice(0,c)),redactAiText(l)},"clean"),o={...e,request:{...e.request,...e.request.system!==void 0?{system:r(e.request.system)}:{},messages:e.request.messages.slice(0,MAX_MESSAGES*4).map(s=>({role:s.role,content:r(s.content)})),...e.request.tools?{tools:e.request.tools.map(s=>({name:r(s.name,200),...s.description?{description:r(s.description,512)}:{}}))}:{}},...e.response?{response:{...e.response,...e.response.completion!==void 0?{completion:r(e.response.completion)}:{},...e.response.reasoning!==void 0?{reasoning:r(e.response.reasoning)}:{},...e.response.error!==void 0?{error:r(e.response.error,2048)}:{},...e.response.toolCalls?{toolCalls:e.response.toolCalls.map(s=>({...s.id?{id:s.id}:{},name:r(s.name,200),...s.arguments!==void 0?{arguments:r(s.arguments,8192)}:{}}))}:{}}}:{}};return e.request.messages.length>MAX_MESSAGES*4&&(t.cut=!0),t.cut&&(o.truncated=!0),o}u(sanitize,"sanitize"),i(sanitize,"sanitize");function clipTo(e,n,t){const r={cut:e.truncated===!0},o=i((a,m=n)=>{if(a!==void 0)return a.length>m&&(r.cut=!0),(0,redact_1.clip)(a,m)},"c"),s=e.request.messages;let d=s;if(s.length>t){const a=Math.min(2,t);d=[...s.slice(0,a),...s.slice(s.length-(t-a))],r.cut=!0}const c=e.response,l={...e,request:{...e.request,...e.request.system!==void 0?{system:o(e.request.system)}:{},messages:d.map(a=>({role:a.role,content:o(a.content)}))},...c?{response:{...c,...c.completion!==void 0?{completion:o(c.completion)}:{},...c.reasoning!==void 0?{reasoning:o(c.reasoning)}:{},...c.toolCalls?{toolCalls:c.toolCalls.map(a=>({...a,...a.arguments!==void 0?{arguments:o(a.arguments,Math.min(n,8192))}:{}}))}:{}}}:{}};return r.cut?l.truncated=!0:delete l.truncated,l}u(clipTo,"clipTo"),i(clipTo,"clipTo");function fitAiContent(e){const n=maxFileBytes();let t=maxFieldChars(),r=MAX_MESSAGES;const o=sanitize(e,t);let s=clipTo(o,t,r),d=JSON.stringify(s);for(;d.length>n&&(t>256||r>2);)t>256?t=Math.max(256,Math.floor(t/2)):r=Math.max(2,Math.floor(r/2)),s=clipTo(o,t,r),d=JSON.stringify(s);return{json:d,content:s}}u(fitAiContent,"fitAiContent"),i(fitAiContent,"fitAiContent");let writes=0;function prune(e){try{const n=readdirSync(e).filter(r=>/^ai_[0-9a-z]+\.json$/.test(r)).sort(),t=n.length-maxFiles();for(let r=0;r<t;r+=1)try{unlinkSync(`${e}/${n[r]}`)}catch{}}catch{}}u(prune,"prune"),i(prune,"prune");function removeAiContent(e){try{unlinkSync((0,ai_content_1.aiContentFile)(emit_1.EVENT_FILE,e))}catch{}}u(removeAiContent,"removeAiContent"),i(removeAiContent,"removeAiContent");function writeAiContent(e){try{const n=(0,ai_content_1.aiContentDir)(emit_1.EVENT_FILE);mkdirSync(n,{recursive:!0,mode:448});const{json:t,content:r}=fitAiContent(e),o=`${n}/${e.id}.json`;return writeFileSync(`${o}.tmp`,t,{mode:384}),renameSync(`${o}.tmp`,o),writes+=1,(writes===1||writes%20===0)&&prune(n),{id:e.id,truncated:r.truncated===!0}}catch{return}}u(writeAiContent,"writeAiContent"),i(writeAiContent,"writeAiContent");
|
package/lib/emit.d.ts
CHANGED
|
@@ -35,9 +35,14 @@ export interface FlowCtx {
|
|
|
35
35
|
requestId?: string;
|
|
36
36
|
originNodeId?: string;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns whether the event was actually written. Almost every caller ignores it; a producer that
|
|
40
|
+
* wrote something ELSE to go with the event (the AI tap's sidecar file) needs to know when the flood
|
|
41
|
+
* guard dropped the event, or it leaves that file behind with nothing pointing at it.
|
|
42
|
+
*/
|
|
38
43
|
export declare function lensEmit(source: string, severity: LensSeverity, title: string, raw: Record<string, unknown>, fingerprint?: string, flowCtx?: FlowCtx,
|
|
39
44
|
/** Event category. Defaults to `cluster` — what every tap emitted before this existed. */
|
|
40
|
-
category?: string):
|
|
45
|
+
category?: string): boolean;
|
|
41
46
|
/**
|
|
42
47
|
* Emit a raw service log line — the ACTUAL stdout/Logger output a pod prints — as
|
|
43
48
|
* a `runtime` event so it surfaces in the dashboard Logs view. Same fs-tap-safe
|
package/lib/emit.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`;maybeRotateEventFile(exports.EVENT_FILE,
|
|
1
|
+
"use strict";var l=Object.defineProperty;var E=(e,r)=>l(e,"name",{value:r,configurable:!0});var a=Object.defineProperty,c=E((e,r)=>a(e,"name",{value:r,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.EVENT_FILE=exports.PROJECT=exports.EVENT_MAX_FILES=exports.EVENT_MAX_BYTES=void 0,exports.maybeRotateEventFile=maybeRotateEventFile,exports.isLensWriting=isLensWriting,exports.lensEmit=lensEmit,exports.lensLog=lensLog;const tslib_1=require("tslib"),nodeFs=tslib_1.__importStar(require("node:fs")),node_path_1=require("node:path"),bus_rotation_1=require("@lensmcp/core/bus-rotation"),flood_guard_1=require("./flood-guard"),flow_context_1=require("./flow-context"),appendFileSync=nodeFs.appendFileSync,mkdirSync=nodeFs.mkdirSync,statSync=nodeFs.statSync,renameSync=nodeFs.renameSync,unlinkSync=nodeFs.unlinkSync,openSync=nodeFs.openSync,closeSync=nodeFs.closeSync;exports.EVENT_MAX_BYTES=(()=>{const e=Number(process.env.LENSMCP_EVENT_MAX_BYTES);return Number.isFinite(e)&&e>0?e:50*1024*1024})(),exports.EVENT_MAX_FILES=(()=>{const e=Number(process.env.LENSMCP_EVENT_MAX_FILES);return Number.isFinite(e)&&e>=1&&e<=20?Math.floor(e):5})();const ROTATE_CHECK_BYTES=1024*1024,bytesSinceCheck=new Map;function maybeRotateEventFile(e,r,o){const s=(bytesSinceCheck.get(e)??0)+r;if(s<ROTATE_CHECK_BYTES&&o===void 0){bytesSinceCheck.set(e,s);return}const n=o?.maxBytes??exports.EVENT_MAX_BYTES,t=o?.maxFiles??exports.EVENT_MAX_FILES;try{const i=statSync(e).size;if(bytesSinceCheck.set(e,0),i<=n)return}catch{bytesSinceCheck.set(e,s);return}(0,bus_rotation_1.rotateBusFile)(e,{maxBytes:n,maxFiles:t,fs:{statSync,renameSync,unlinkSync,openSync,closeSync}})}E(maybeRotateEventFile,"maybeRotateEventFile"),c(maybeRotateEventFile,"maybeRotateEventFile"),exports.PROJECT=process.env.LENSMCP_PROJECT||process.env.SERVICE_NAME||"app",exports.EVENT_FILE=process.env.LENSMCP_EVENT_FILE||`${process.cwd()}/.lensmcp/events.jsonl`;let writing=!1,dirReady=!1;const emitGuard=(0,flood_guard_1.createEventFloodGuard)({burst:30,perSec:5}),logGuard=(0,flood_guard_1.createEventFloodGuard)({burst:120,perSec:20});function isLensWriting(){return writing}E(isLensWriting,"isLensWriting"),c(isLensWriting,"isLensWriting");function lensEmit(e,r,o,s,n,t,i="cluster"){try{writing=!0;const d=n??`${e}:${String(s.name??e)}`,p=emitGuard.admit(d);if(!p.ok)return!1;dirReady||(mkdirSync((0,node_path_1.dirname)(exports.EVENT_FILE),{recursive:!0}),dirReady=!0);const u=JSON.stringify({id:Date.now().toString(36)+Math.random().toString(36).slice(2,10),sessionId:"pending",timestamp:Date.now(),source:e,category:i,severity:r,context:{sessionId:"pending",projectName:exports.PROJECT,...t?.flowId?{flowId:t.flowId}:{},...t?.requestId?{requestId:t.requestId}:{},...t?.originNodeId?{originNodeId:t.originNodeId}:{}},fingerprint:d,title:o,raw:{project:exports.PROJECT,...s,...p.suppressed>0?{floodSuppressed:p.suppressed}:{}}})+`
|
|
2
|
+
`;return maybeRotateEventFile(exports.EVENT_FILE,u.length),appendFileSync(exports.EVENT_FILE,u),!0}catch{return!1}finally{writing=!1}}E(lensEmit,"lensEmit"),c(lensEmit,"lensEmit");function lensLog(e,r,o){try{writing=!0;const s=e==="error"||e==="fatal"?`log:error:${exports.PROJECT}`:`log:${exports.PROJECT}`,n=logGuard.admit(s);if(!n.ok)return;dirReady||(mkdirSync((0,node_path_1.dirname)(exports.EVENT_FILE),{recursive:!0}),dirReady=!0);const t=(0,flow_context_1.flowContext)(),i=JSON.stringify({id:Date.now().toString(36)+Math.random().toString(36).slice(2,10),sessionId:"pending",timestamp:Date.now(),source:"nestjs",category:"runtime",severity:e,context:{sessionId:"pending",projectName:exports.PROJECT,...t?.flowId?{flowId:t.flowId}:{},...t?.requestId?{requestId:t.requestId}:{}},fingerprint:s,title:r,raw:{project:exports.PROJECT,kind:"log",...o?.stack?{request:{error:{message:r,stack:o.stack}}}:{},...n.suppressed>0?{floodSuppressed:n.suppressed}:{}}})+`
|
|
3
3
|
`;maybeRotateEventFile(exports.EVENT_FILE,i.length),appendFileSync(exports.EVENT_FILE,i)}catch{}finally{writing=!1}}E(lensLog,"lensLog"),c(lensLog,"lensLog");
|
package/lib/install.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var p=Object.defineProperty;var a=(t,r)=>p(t,"name",{value:r,configurable:!0});var s=Object.defineProperty,e=a((t,r)=>s(t,"name",{value:r,configurable:!0}),"e");Object.defineProperty(exports,"__esModule",{value:!0}),exports.installLensmcpNodeInstrumentation=installLensmcpNodeInstrumentation;const require_hook_1=require("./require-hook"),flow_context_1=require("./flow-context"),tap_egress_1=require("./tap-egress"),tap_fs_1=require("./tap-fs"),tap_exec_1=require("./tap-exec"),tap_pg_1=require("./tap-pg"),tap_redis_1=require("./tap-redis"),active_work_1=require("./active-work"),tap_bullmq_1=require("./tap-bullmq"),tap_nest_1=require("./tap-nest"),tap_event_loop_1=require("./tap-event-loop"),tap_stdout_1=require("./tap-stdout");function installLensmcpNodeInstrumentation(){const t=globalThis;if(!t.__lensmcpNodeInstrumented&&(t.__lensmcpNodeInstrumented=!0,process.env.LENSMCP_AUTO!=="0")){try{(0,active_work_1.installActiveWorkProbe)()}catch{}try{(0,flow_context_1.primeFlowContext)()}catch{}try{(0,tap_egress_1.tapEgress)()}catch{}try{(0,tap_fs_1.tapFs)()}catch{}try{(0,tap_exec_1.tapExec)()}catch{}try{(0,tap_stdout_1.tapStdout)()}catch{}try{(0,tap_event_loop_1.tapEventLoop)()}catch{}try{(0,require_hook_1.installRequireHook)({pg:tap_pg_1.pgTap,ioredis:tap_redis_1.redisTap,bullmq:tap_bullmq_1.bullmqTap,"@nestjs/core":tap_nest_1.nestTap})}catch{}try{(0,tap_nest_1.installChildAppBridge)()}catch{}}}a(installLensmcpNodeInstrumentation,"installLensmcpNodeInstrumentation"),e(installLensmcpNodeInstrumentation,"installLensmcpNodeInstrumentation");
|
|
1
|
+
"use strict";var p=Object.defineProperty;var a=(t,r)=>p(t,"name",{value:r,configurable:!0});var s=Object.defineProperty,e=a((t,r)=>s(t,"name",{value:r,configurable:!0}),"e");Object.defineProperty(exports,"__esModule",{value:!0}),exports.installLensmcpNodeInstrumentation=installLensmcpNodeInstrumentation;const require_hook_1=require("./require-hook"),flow_context_1=require("./flow-context"),tap_egress_1=require("./tap-egress"),tap_ai_1=require("./tap-ai"),tap_fs_1=require("./tap-fs"),tap_exec_1=require("./tap-exec"),tap_pg_1=require("./tap-pg"),tap_redis_1=require("./tap-redis"),active_work_1=require("./active-work"),tap_bullmq_1=require("./tap-bullmq"),tap_nest_1=require("./tap-nest"),tap_event_loop_1=require("./tap-event-loop"),tap_stdout_1=require("./tap-stdout");function installLensmcpNodeInstrumentation(){const t=globalThis;if(!t.__lensmcpNodeInstrumented&&(t.__lensmcpNodeInstrumented=!0,process.env.LENSMCP_AUTO!=="0")){try{(0,active_work_1.installActiveWorkProbe)()}catch{}try{(0,flow_context_1.primeFlowContext)()}catch{}try{(0,tap_egress_1.tapEgress)()}catch{}try{(0,tap_ai_1.tapAi)()}catch{}try{(0,tap_fs_1.tapFs)()}catch{}try{(0,tap_exec_1.tapExec)()}catch{}try{(0,tap_stdout_1.tapStdout)()}catch{}try{(0,tap_event_loop_1.tapEventLoop)()}catch{}try{(0,require_hook_1.installRequireHook)({pg:tap_pg_1.pgTap,ioredis:tap_redis_1.redisTap,bullmq:tap_bullmq_1.bullmqTap,"@nestjs/core":tap_nest_1.nestTap})}catch{}try{(0,tap_nest_1.installChildAppBridge)()}catch{}}}a(installLensmcpNodeInstrumentation,"installLensmcpNodeInstrumentation"),e(installLensmcpNodeInstrumentation,"installLensmcpNodeInstrumentation");
|
package/lib/tap-ai.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type AiCaptureMode = 'off' | 'metadata' | 'content';
|
|
2
|
+
export declare function aiCaptureMode(env?: Record<string, string | undefined>): AiCaptureMode;
|
|
3
|
+
/** Resolve once every in-flight capture has been written — for tests, which assert on the file. */
|
|
4
|
+
export declare function flushAiTapForTests(): Promise<void>;
|
|
5
|
+
/** Undo the wrapper and clear the install marker (same trap `resetEgressTapForTests` documents). */
|
|
6
|
+
export declare function resetAiTapForTests(): void;
|
|
7
|
+
export declare function tapAi(): void;
|
package/lib/tap-ai.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var R=Object.defineProperty;var c=(t,e)=>R(t,"name",{value:e,configurable:!0});var x=Object.defineProperty,d=c((t,e)=>x(t,"name",{value:e,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.aiCaptureMode=aiCaptureMode,exports.flushAiTapForTests=flushAiTapForTests,exports.resetAiTapForTests=resetAiTapForTests,exports.tapAi=tapAi;const emit_1=require("./emit"),flow_context_1=require("./flow-context"),ai_providers_1=require("./ai-providers"),ai_store_1=require("./ai-store");function aiCaptureMode(t=process.env){const e=(t.LENSMCP_AI_CAPTURE??"").trim().toLowerCase();return e==="content"?"content":e==="metadata"||e==="1"||e==="true"||e==="on"?"metadata":"off"}c(aiCaptureMode,"aiCaptureMode"),d(aiCaptureMode,"aiCaptureMode");const MAX_REQUEST_PARSE_BYTES=2*1024*1024,MAX_RESPONSE_PARSE_BYTES=8*1024*1024,MAX_STREAM_BYTES=8*1024*1024,MAX_STREAM_MS=300*1e3,MAX_ENDPOINT_CHARS=256;function locate(t){try{const e=new URL(typeof t=="string"?t:t instanceof URL?t.href:t.url);return{host:e.hostname,port:e.port,pathname:e.pathname}}catch{return}}c(locate,"locate"),d(locate,"locate");const methodOf=d((t,e)=>{const o=typeof t=="object"&&t!==null&&!(t instanceof URL)?t.method:void 0;return String(e?.method??o??"GET").toUpperCase()},"methodOf"),decode=d(t=>({bytes:t.byteLength,...t.byteLength<=MAX_REQUEST_PARSE_BYTES?{text:Buffer.from(t.buffer,t.byteOffset,t.byteLength).toString("utf8")}:{}}),"decode");function takeRequestBody(t,e){const o=e?.body;if(typeof o=="string")return{text:o,bytes:Buffer.byteLength(o)};if(o instanceof ArrayBuffer)return decode(new Uint8Array(o));if(ArrayBuffer.isView(o))return decode(new Uint8Array(o.buffer,o.byteOffset,o.byteLength));if(typeof Blob<"u"&&o instanceof Blob)return{bytes:o.size,...o.size<=MAX_REQUEST_PARSE_BYTES?{pending:o.text().catch(()=>{})}:{}};if(o==null&&typeof t=="object"&&t!==null&&!(t instanceof URL)){const s=t;if(!s.bodyUsed&&s.body)try{return{pending:s.clone().text().catch(()=>{})}}catch{return{}}}return{}}c(takeRequestBody,"takeRequestBody"),d(takeRequestBody,"takeRequestBody");const sum=d(t=>t.reduce((e,o)=>e+o,0),"sum");function parseJson(t){if(t)try{return JSON.parse(t)}catch{return}}c(parseJson,"parseJson"),d(parseJson,"parseJson");function headerOf(t,e){try{return t?.headers?.get?.(e)??""}catch{return""}}c(headerOf,"headerOf"),d(headerOf,"headerOf");const statusOf=d(t=>typeof t?.status=="number"?t.status:void 0,"statusOf");function failureKind(t,e){const o=t?.name,s=o==="TimeoutError";return{timedOut:s,aborted:!s&&(o==="AbortError"||e?.aborted===!0)}}c(failureKind,"failureKind"),d(failureKind,"failureKind");async function readStream(t,e,o){const s=(0,ai_providers_1.createStreamAccumulator)(e);let i,r=!1;const a=t.body;if(a){const f=a.getReader(),n=new TextDecoder;let p=0;try{for(;;){const{done:m,value:g}=await f.read();if(m)break;if(p+=g.byteLength,s.push(n.decode(g,{stream:!0})),i===void 0&&s.eventCount>0&&(i=Date.now()-o),p>MAX_STREAM_BYTES||Date.now()-o>MAX_STREAM_MS){r=!0,await f.cancel().catch(()=>{});break}}}catch{r=!0}}return{response:s.end(),sse:!0,cut:r,...i!==void 0?{firstEventMs:i}:{}}}c(readStream,"readStream"),d(readStream,"readStream");async function readBody(t){const e={completion:"",toolCalls:[],usage:{}};if(!t.res||!t.clone)return{response:e,sse:!1,cut:!1};try{if(headerOf(t.res,"content-type").includes("text/event-stream"))return await readStream(t.clone,t.target,t.startedAt);const o=Number(headerOf(t.res,"content-length"));if(Number.isFinite(o)&&o>MAX_RESPONSE_PARSE_BYTES)return{response:e,sse:!1,cut:!1};const s=await t.clone.text().catch(()=>{});if(s&&/^\s*(?:event|data):/.test(s)){const i=(0,ai_providers_1.createStreamAccumulator)(t.target);return i.push(s),{response:i.end(),sse:!0,cut:!1}}return{response:(0,ai_providers_1.normalizeResponse)(t.target,parseJson(s)),sse:!1,cut:!1}}catch{return{response:e,sse:!1,cut:!1}}}c(readBody,"readBody"),d(readBody,"readBody");async function capture(t){const e=readBody(t),o=t.request.text??await t.request.pending,s=t.request.bytes??(o?Buffer.byteLength(o):void 0),i=s!==void 0&&s>MAX_REQUEST_PARSE_BYTES,r=(0,ai_providers_1.normalizeRequest)(t.target,i?void 0:parseJson(o)),a=statusOf(t.res),f=await e;let n=f.response;const p=r.stream||f.sse,m=f.firstEventMs,g=f.cut,{aborted:y,timedOut:T}=t.failure!==void 0?failureKind(t.failure,t.signal):{aborted:!1,timedOut:!1};t.failure!==void 0&&(n={...n,error:String(t.failure?.message??t.failure)});const b=y||g,A=Date.now()-t.startedAt,u=n.model??r.model,_=sum(r.messages.map(w=>w.content.length))+(r.system?.length??0);let l,h=!1;if(t.mode==="content"&&t.target.api!=="embeddings"){const w={schemaVersion:1,id:(0,ai_store_1.newAiCallId)(),capturedAt:Date.now(),...t.ctx?.flowId?{flowId:t.ctx.flowId}:{},...t.ctx?.requestId?{requestId:t.ctx.requestId}:{},provider:t.target.provider,api:t.target.api,...u?{model:u}:{},host:t.host,endpoint:t.endpoint,stream:p,...a!==void 0?{status:a}:{},request:{...r.system?{system:r.system}:{},messages:r.messages,...r.tools.length?{tools:r.tools}:{},...Object.keys(r.params).length?{params:r.params}:{}},response:{completion:n.completion,...n.reasoning?{reasoning:n.reasoning}:{},...n.toolCalls.length?{toolCalls:n.toolCalls}:{},...n.finishReason?{finishReason:n.finishReason}:{},usage:n.usage,...n.error?{error:n.error}:{}}},S=(0,ai_store_1.writeAiContent)(w);S&&(l=S.id,h=S.truncated)}const v=a!==void 0&&a>=400||t.failure!==void 0&&!y,C=u??"?";!(0,emit_1.lensEmit)("ai",t.failure!==void 0&&!y?"error":"info",a!==void 0?`ai ${t.target.provider}/${C} ${t.target.api} \u2192 ${a}`:`ai ${t.target.provider}/${C} ${t.target.api} ${y?"aborted":"failed"}`,{name:t.target.provider,kind:"ai-call",provider:t.target.provider,api:t.target.api,...u?{model:u}:{},...r.model&&u&&r.model!==u?{requestedModel:r.model}:{},host:t.host,endpoint:t.endpoint,method:t.method,...a!==void 0?{status:a}:{},stream:p,startedAt:t.startedAt,durationMs:A,...t.ttfbMs!==void 0?{ttfbMs:t.ttfbMs}:{},...m!==void 0?{firstEventMs:m}:{},...Object.keys(n.usage).length?{usage:n.usage}:{},...n.finishReason?{finishReason:n.finishReason}:{},toolCallCount:n.toolCalls.length,sizes:{messageCount:r.messages.length,toolCount:r.tools.length,promptChars:_,completionChars:n.completion.length,...s!==void 0?{requestBytes:s}:{}},...r.inputCount!==void 0?{inputCount:r.inputCount}:{},...n.vectorCount!==void 0?{vectorCount:n.vectorCount}:{},contentCaptured:l!==void 0,...l?{contentRef:l}:{},...h?{truncated:!0}:{},...b?{aborted:!0}:{},...T?{timedOut:!0}:{},...n.error?{error:(0,ai_store_1.redactAiText)(n.error).slice(0,300)}:{}},v?`ai:error:${t.target.provider}:${C}`:`ai:${t.target.provider}:${C}`,t.ctx,"ai")&&l&&(0,ai_store_1.removeAiContent)(l)}c(capture,"capture"),d(capture,"capture");let savedFetch;const pending=new Set;async function flushAiTapForTests(){await Promise.allSettled([...pending])}c(flushAiTapForTests,"flushAiTapForTests"),d(flushAiTapForTests,"flushAiTapForTests");function resetAiTapForTests(){const t=globalThis;savedFetch&&(t.fetch=savedFetch),delete t.__lensmcpAiTap,savedFetch=void 0,pending.clear()}c(resetAiTapForTests,"resetAiTapForTests"),d(resetAiTapForTests,"resetAiTapForTests");function tapAi(){const t=aiCaptureMode();if(t==="off")return;const e=globalThis;if(typeof e.fetch!="function"||e.__lensmcpAiTap)return;e.__lensmcpAiTap=!0;const o=(0,ai_providers_1.parseExtraHosts)(process.env.LENSMCP_AI_HOSTS),s=e.fetch.bind(globalThis);savedFetch=e.fetch,e.fetch=(async(i,r)=>{const a=locate(i),f=methodOf(i,r),n=a&&f==="POST"?(0,ai_providers_1.matchTarget)(a.host,a.port,a.pathname,o):void 0;if(!a||!n)return s(i,r);const p=Date.now(),m=(0,flow_context_1.flowContext)(),g=a.pathname.length>MAX_ENDPOINT_CHARS?`${a.pathname.slice(0,MAX_ENDPOINT_CHARS)}\u2026`:a.pathname,y=takeRequestBody(i,r),T=r?.signal??(typeof i=="object"&&i!==null&&!(i instanceof URL)?i.signal:void 0),b=d(h=>{const v=capture(h).catch(()=>{});pending.add(v),v.finally(()=>pending.delete(v))},"track"),A={mode:t,target:n,host:a.host,endpoint:g,method:f,startedAt:p,ctx:m,signal:T,request:y};let u;try{u=await s(i,r)}catch(h){throw b({...A,failure:h}),h}const _=Date.now()-p;let l;try{l=u.clone()}catch{l=void 0}return b({...A,ttfbMs:_,res:u,...l?{clone:l}:{}}),u})}c(tapAi,"tapAi"),d(tapAi,"tapAi");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/node-instrumentation",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.6",
|
|
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.23.
|
|
19
|
-
"@lensmcp/nest-instrumentation": "1.23.
|
|
20
|
-
"@lensmcp/protocol-types": "1.23.
|
|
18
|
+
"@lensmcp/core": "1.23.6",
|
|
19
|
+
"@lensmcp/nest-instrumentation": "1.23.6",
|
|
20
|
+
"@lensmcp/protocol-types": "1.23.6",
|
|
21
21
|
"tslib": "^2.3.0"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|