@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
158 lines (157 loc) • 6.6 kB
JavaScript
/**
* Artifact reading primitives shared by every backend and every reader.
*
* Two readers exist — the model's `retrieve_context({ artifactId })` and the
* host's `readArtifact()` — and two shipped backends. Everything they agree
* on lives here so it is agreed on exactly once:
*
* - `readArtifactWindow` is THE paged read. It asks a backend for a window
* when the backend can produce one (`retrieveRange`), and otherwise reads
* the whole payload and slices. A reader never has to know which.
* - `searchArtifactContent` is the literal search over an artifact. It
* returns bounded snippets with character offsets, never whole lines:
* an MCP artifact is usually one compact JSON line, so "the matching line"
* would be the payload the search was meant to avoid reading.
* - `isSafeArtifactId` is the shape check every backend applies before an
* id reaches a path or a key. Ids arrive from the model.
*
* @module artifacts/artifactReader
*/
/** Characters used for the quick preview embedded in surrogate results. */
export const DEFAULT_PREVIEW_CHARS = 500;
/** Matches returned by one artifact search; the rest are counted, not sent. */
export const MAX_ARTIFACT_SEARCH_MATCHES = 50;
/** Longest search pattern accepted — bounds the work per call. */
export const MAX_SEARCH_PATTERN_CHARS = 200;
/** Characters kept on each side of a hit in its snippet. */
const SNIPPET_CONTEXT_CHARS = 120;
/**
* Ids that may be turned into a path or a key.
*
* Ids reach the backends straight from the model through `retrieve_context`.
* No dots and no separators means `../../etc/passwd` can never become a file
* probe, and no glob characters means an id can never widen a key pattern.
* Real ids are UUIDs.
*/
const SAFE_ARTIFACT_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
/** True when `id` is shaped like something a backend may look up. */
export function isSafeArtifactId(id) {
return SAFE_ARTIFACT_ID.test(id);
}
/** Head slice of a payload for surrogate headers, ellipsised when cut. */
export function generateArtifactPreview(payload, chars = DEFAULT_PREVIEW_CHARS) {
if (payload.length <= chars) {
return payload;
}
return `${payload.slice(0, chars)}…`;
}
/** Cut one window out of a payload already in memory. */
export function sliceArtifactWindow(content, page) {
const offset = Math.max(0, page?.offset ?? 0);
const limit = page?.limit;
const window = limit === undefined
? content.slice(offset)
: content.slice(offset, offset + Math.max(0, limit));
return { content: window, offset, totalLength: content.length };
}
/**
* Read one window of an artifact through whatever the backend supports.
*
* With a `page` and a backend that implements `retrieveRange`, only the
* window crosses the wire. Otherwise the whole payload is fetched and cut
* here. Without a `page` the whole payload is always fetched — a caller that
* omits the window is asking for the artifact.
*
* Returns `null` when the id is unknown, unsafe, or expired.
*/
export async function readArtifactWindow(store, id, page) {
if (page && store.retrieveRange) {
return store.retrieveRange(id, {
offset: Math.max(0, page.offset ?? 0),
limit: page.limit === undefined ? undefined : Math.max(0, page.limit),
});
}
const content = await store.retrieve(id);
if (content === null) {
return null;
}
return sliceArtifactWindow(content, page);
}
/**
* Why a search pattern cannot be used, or `undefined` when it can.
*
* Empty would match at every position; over-long bounds the scan. Both are
* reported to the model rather than silently ignored — the whole point of
* the artifact search is that "search did not happen" is never invisible.
*/
export function validateSearchPattern(pattern) {
if (pattern.length === 0) {
return "Search pattern must not be empty";
}
if (pattern.length > MAX_SEARCH_PATTERN_CHARS) {
return `Search pattern too long (max ${MAX_SEARCH_PATTERN_CHARS} chars)`;
}
return undefined;
}
/**
* Literal, case-insensitive search over an artifact.
*
* Regex metacharacters in `pattern` are matched literally — the model's
* input is never compiled as a regex, so it cannot be made catastrophic.
* Each hit carries the character `offset` of the match (what to pass back as
* `offset` for a targeted read), its 1-based line, and a bounded snippet.
*
* Scanning starts at `from`, so a caller can walk a long payload by passing
* `nextSearchOffset` back in. Every match after `from` is counted in
* `totalMatches`; only the first `maxMatches` are returned.
*
* `pattern` must already have passed {@link validateSearchPattern}.
*/
export function searchArtifactContent(content, pattern, options) {
const from = Math.max(0, options?.from ?? 0);
const maxMatches = options?.maxMatches ?? MAX_ARTIFACT_SEARCH_MATCHES;
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(escaped, "gi");
regex.lastIndex = from;
const matches = [];
let totalMatches = 0;
let nextSearchOffset;
// Line numbers are counted incrementally from the start of the payload so
// the whole scan stays a single pass.
let line = 1;
let lineScanPos = 0;
let match;
while ((match = regex.exec(content)) !== null) {
totalMatches += 1;
if (matches.length < maxMatches) {
for (let i = lineScanPos; i < match.index; i += 1) {
if (content.charCodeAt(i) === 10) {
line += 1;
}
}
lineScanPos = match.index;
const snippetOffset = Math.max(0, match.index - SNIPPET_CONTEXT_CHARS);
const snippetEnd = Math.min(content.length, match.index + match[0].length + SNIPPET_CONTEXT_CHARS);
matches.push({
offset: match.index,
line,
snippetOffset,
snippet: content.slice(snippetOffset, snippetEnd),
});
}
else if (nextSearchOffset === undefined) {
nextSearchOffset = match.index;
}
if (match[0].length === 0) {
// Unreachable for a non-empty pattern; keeps the loop finite regardless.
regex.lastIndex += 1;
}
}
return {
matches,
matchCount: matches.length,
totalMatches,
truncated: totalMatches > matches.length,
...(nextSearchOffset === undefined ? {} : { nextSearchOffset }),
};
}