@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
635 lines (634 loc) • 21.7 kB
TypeScript
/**
* Context Types for NeuroLink - Factory Pattern Implementation
* Provides type-safe context integration for AI generation
*/
import type { ExecutionContext } from "../types/tools.js";
import type { JsonObject } from "./common.js";
import type { ChatMessage, ConversationMemoryConfig } from "./conversation.js";
import type { DecisionCallerFn } from "./decision.js";
/**
* Base context type for all AI operations
*/
export type BaseContext = {
userId?: string;
sessionId?: string;
requestId?: string;
userRole?: string;
userPreferences?: JsonObject;
userMetadata?: JsonObject;
applicationContext?: {
name: string;
version?: string;
environment?: "development" | "staging" | "production";
};
organizationId?: string;
departmentId?: string;
projectId?: string;
[key: string]: unknown;
};
/**
* Context integration mode types
*/
type ContextIntegrationMode = "prompt_prefix" | "prompt_suffix" | "system_prompt" | "metadata_only" | "structured_prompt" | "none";
/**
* Context configuration for AI generation
*/
export type ContextConfig = {
mode: ContextIntegrationMode;
includeInPrompt?: boolean;
includeInAnalytics?: boolean;
includeInEvaluation?: boolean;
template?: string;
maxLength?: number;
};
/**
* Context processing result
*/
type ProcessedContext = {
originalContext: BaseContext;
processedContext: string | null;
config: ContextConfig;
metadata: {
truncated: boolean;
processingTime: number;
template: string;
mode: ContextIntegrationMode;
};
};
/**
* Configuration for framework fields exclusion
* Can be customized per application or environment
*/
export type FrameworkFieldsConfig = {
defaultFields: string[];
additionalFields?: string[];
overrideFields?: string[];
includeFields?: string[];
};
/**
* Factory for context processing
*/
export declare class ContextFactory {
/**
* Default framework fields configuration
*/
static readonly DEFAULT_FRAMEWORK_FIELDS: FrameworkFieldsConfig;
/**
* Current framework fields configuration
*/
private static frameworkFieldsConfig;
/**
* Flag to track if framework fields have been initialized
*/
private static isFrameworkFieldsInitialized;
/**
* Configure framework fields for exclusion from custom data
*/
static configureFrameworkFields(config: Partial<FrameworkFieldsConfig>): void;
/**
* Get current framework fields configuration
* Ensures lazy initialization if not already loaded
*/
static getFrameworkFieldsConfig(): FrameworkFieldsConfig;
/**
* Reset framework fields configuration to default
*/
static resetFrameworkFieldsConfig(): void;
/**
* Load framework fields configuration from environment variables
* Supports NEUROLINK_CONTEXT_EXCLUDE_FIELDS and NEUROLINK_CONTEXT_INCLUDE_FIELDS
*/
static loadFrameworkFieldsFromEnv(): void;
/**
* Add additional fields to exclude
*/
static addFrameworkFieldsToExclude(fields: string[]): void;
/**
* Add fields to include (override exclusion)
*/
static addFrameworkFieldsToInclude(fields: string[]): void;
/**
* Default context configuration
*/
static readonly DEFAULT_CONFIG: ContextConfig;
/**
* Validate and normalize context data
*/
static validateContext(context: unknown): BaseContext | null;
/**
* Process context for AI generation based on configuration
*/
static processContext(context: BaseContext, config?: Partial<ContextConfig>): ProcessedContext;
/**
* Format context for prompt integration
*/
private static formatContextForPrompt;
/**
* Format context as prompt prefix
*/
private static formatAsPrefix;
/**
* Format context as prompt suffix
*/
private static formatAsSuffix;
/**
* Format context for system prompt
*/
private static formatForSystemPrompt;
/**
* Format context in structured format
*/
private static formatStructured;
/**
* Extract analytics data from context
*/
static extractAnalyticsContext(context: BaseContext): JsonObject;
/**
* Extract evaluation context
*/
static extractEvaluationContext(context: BaseContext): JsonObject;
}
/**
* Context conversion utilities for domain-specific data
* Replaces hardcoded business context with generic domain context
*/
type ContextConversionOptions = {
preserveLegacyFields?: boolean;
validateDomainData?: boolean;
includeMetadata?: boolean;
};
export declare class ContextConverter {
/**
* Convert legacy business context to generic domain context
* Based on business context patterns
*/
static convertBusinessContext(legacyContext: Record<string, unknown>, domainType: string, options?: ContextConversionOptions): ExecutionContext;
/**
* Create execution context for required domain
*/
static createDomainContext(domainType: string, domainData: Record<string, unknown>, sessionInfo?: {
sessionId?: string;
userId?: string;
}): ExecutionContext;
private static inferProvider;
private static extractCustomData;
}
/** Stages available in the compaction pipeline. */
export type CompactionStage = "relevance" | "prune" | "deduplicate" | "summarize" | "truncate";
/** Result of multi-stage context compaction. */
export type CompactionResult = {
compacted: boolean;
stagesUsed: CompactionStage[];
tokensBefore: number;
tokensAfter: number;
tokensSaved: number;
messages: ChatMessage[];
};
/** Configuration for the context compaction pipeline. */
export type CompactionConfig = {
enablePrune?: boolean;
enableDeduplicate?: boolean;
enableSummarize?: boolean;
enableTruncate?: boolean;
pruneProtectTokens?: number;
pruneMinimumSavings?: number;
pruneProtectedTools?: string[];
summarizationProvider?: string;
summarizationModel?: string;
keepRecentRatio?: number;
truncationFraction?: number;
provider?: string;
};
/** Result of a context budget check. */
export type BudgetCheckResult = {
/** Whether the request fits within the context window */
withinBudget: boolean;
/** Estimated total input tokens */
estimatedInputTokens: number;
/** Available input tokens for this model */
availableInputTokens: number;
/** Usage ratio (0.0 - 1.0+) */
usageRatio: number;
/** Whether auto-compaction should trigger */
shouldCompact: boolean;
/** Breakdown of token usage by category */
breakdown: {
systemPrompt: number;
conversationHistory: number;
currentPrompt: number;
toolDefinitions: number;
fileAttachments: number;
};
};
/** Parameters for budget checking. */
export type BudgetCheckParams = {
provider: string;
model?: string;
maxTokens?: number;
systemPrompt?: string;
conversationMessages?: Array<{
role: string;
content: string;
}>;
currentPrompt?: string;
toolDefinitions?: unknown[];
fileAttachments?: Array<{
content: string;
}>;
/** Compaction trigger threshold (0.0-1.0). Default: 0.80 */
compactionThreshold?: number;
};
/** A file prepared for potential summarization. */
export type FileForSummarization = {
/** Display name (e.g. "report.pdf") */
fileName: string;
/** Human-readable type label (e.g. "PDF Document") */
fileType: string;
/** Extracted text content */
content: string;
/** Estimated token count (provider-adjusted) */
estimatedTokens: number;
/** Optional MIME type */
mimeType?: string;
/** Original byte size on disk */
originalSize?: number;
};
/** Parameters for `shouldSummarizeFiles()`. */
export type FileSummarizationCheckParams = {
/** AI provider name (e.g. "vertex", "anthropic") */
provider: string;
/** Model name (optional -- falls back to provider default) */
model?: string;
/** Token estimate for the system prompt */
systemPromptTokens: number;
/** Token estimate for conversation history */
conversationHistoryTokens: number;
/** Token estimate for the current user prompt */
currentPromptTokens: number;
/** Token estimate for tool definitions */
toolDefinitionTokens: number;
/** Token estimate for all attached files (sum) */
fileTokens: number;
/** Number of attached files */
fileCount?: number;
/** Explicit maxTokens (output reserve) from user config */
maxTokens?: number;
/** Context usage fraction that triggers summarization (0.0-1.0, default 0.80) */
threshold?: number;
/** Minimum tokens per file in the summarization plan */
minTokensPerFile?: number;
/** Maximum tokens per file in the summarization plan */
maxTokensPerFile?: number;
};
/** Result of `shouldSummarizeFiles()`. */
export type FileSummarizationCheckResult = {
/** Whether summarization is needed */
needsSummarization: boolean;
/** Total estimated input tokens (all categories) */
totalEstimatedTokens: number;
/** Available input tokens for the model */
availableInputTokens: number;
/** Budget remaining for files after non-file content */
availableBudgetForFiles: number;
/** If summarizing, the per-file token budget (undefined when not needed) */
perFileBudget?: number;
};
/** Parameters for `buildFileSummarizationPrompt()`. */
export type FileSummarizationPromptParams = {
/** File display name */
fileName: string;
/** File type label */
fileType: string;
/** Full extracted text of the file */
fileContent: string;
/** The user's original prompt / question */
userPrompt: string;
/** Target output token count for the summary */
targetTokens: number;
};
/** Result item from `planFileSummarization()`. */
export type SummarizedFile = {
/** File display name */
fileName: string;
/** File type label */
fileType: string;
/** Summary text (or original content if not summarized) */
summary: string;
/** Original token estimate */
originalTokens: number;
/** Token estimate of the summary */
summaryTokens: number;
/** Whether this file was actually summarized */
wasSummarized: boolean;
};
/** Plan entry for a single file. */
export type FileSummarizationPlanEntry = {
file: FileForSummarization;
action: "summarize" | "keep";
targetTokens?: number;
};
/** Raw file input before text extraction. */
export type RawFileInput = {
/** File content -- either a UTF-8 string or a raw Buffer */
content: string | Buffer;
/** MIME type (e.g. "application/pdf", "text/plain") */
mimeType: string;
/** Display file name */
fileName: string;
/** Original byte size on disk (optional) */
originalSize?: number;
};
/** Input file for aggregate budget enforcement. */
export type BudgetFileInput = {
name: string;
sizeBytes: number;
/** Optional file type hint for type-aware token estimation */
fileType?: string;
};
/**
* @deprecated Use ToolOutputPreviewOptions instead.
*/
export type TruncateOptions = {
maxBytes?: number;
maxLines?: number;
direction?: "head" | "tail";
saveToDisk?: boolean;
saveDir?: string;
};
/**
* @deprecated Use ToolOutputPreviewResult instead.
*/
export type TruncateResult = {
content: string;
truncated: boolean;
savedPath?: string;
originalSize: number;
};
/** Options for tool output preview generation. */
export type ToolOutputPreviewOptions = {
/** Maximum bytes for the preview (default: 50KB) */
maxBytes?: number;
/** Maximum lines for the preview (default: 2000) */
maxLines?: number;
/** Fraction of preview budget allocated to the head (default: 0.25) */
headRatio?: number;
/** Fraction of preview budget allocated to the tail (default: 0.75) */
tailRatio?: number;
/**
* Override the omission notice spliced between head and tail. A string is
* used verbatim; a function receives the omitted byte count and returns
* the notice text. When omitted, the built-in default is used, which
* names the `retrieve_context` tool — pass this when that tool is not
* registered on the calling instance so the model isn't pointed at a tool
* that doesn't exist.
*/
notice?: string | ((omittedBytes: number) => string);
};
/** Result of tool output preview generation. */
export type ToolOutputPreviewResult = {
/** The preview string (or full output if under limits) */
preview: string;
/** Whether truncation was applied */
truncated: boolean;
/** Original byte size of the full output */
originalSize: number;
};
/** Result of tool pair repair. */
export type RepairResult = {
repaired: boolean;
messages: ChatMessage[];
orphanedCallsFixed: number;
orphanedResultsFixed: number;
};
/**
* Provider-neutral view of ONE message in an agent loop's history.
*
* Every native provider loop keeps its history in a different shape (AI-SDK
* `ModelMessage`, OpenAI-compatible `{role,tool_calls}`, Gemini `contents`
* parts, Anthropic content blocks). The reclaim POLICY is identical across all
* of them, so adapters map their own shape onto this view, ask the core what to
* do, and apply the answer themselves.
*/
export type LoopGuardEntry = {
/** `toolCall` and `toolResult` form the batches the policy keeps intact. */
kind: "other" | "toolCall" | "toolResult";
/** Estimated tokens this entry currently costs. */
tokens: number;
/**
* Tokens this entry would cost with its payload replaced by a head/tail
* preview. Omitted when the entry cannot usefully shrink — which is exactly
* the case that forces the policy to drop batches instead.
*/
previewTokens?: number;
};
/** What the caller should do to reclaim budget. Indices refer to the input array. */
export type LoopGuardPlan = {
/** False when the loop is under threshold and nothing should change. */
fire: boolean;
/** Entries whose payload should be replaced by a preview. */
truncate: number[];
/** Entries to remove entirely — always whole batches, never a partial pair. */
drop: number[];
/** Estimated total after applying the plan, including fixed overhead. */
projectedTokens: number;
};
/**
* Structural view of one Anthropic content block, loose enough to accept the
* official SDK's `ContentBlockParam` union and NeuroLink's own
* `VertexAnthropicMessage` blocks without a cast at either call site.
*/
export type AnthropicGuardBlock = {
type: string;
/** Payload of a `tool_result` block. Other block kinds carry other fields. */
content?: unknown;
/** Text of a `text` block. */
text?: string;
};
/**
* Structural view of one Anthropic-shaped message, as used by both the direct
* Anthropic loop and the native Vertex+Claude path. Tool calls ride as
* `tool_use` blocks on an assistant message; their answers ride as
* `tool_result` blocks on the following user message.
*/
export type AnthropicGuardMessage = {
/**
* `system` is included because the installed `@anthropic-ai/sdk` widens
* `MessageParam["role"]` to accept it; narrowing here would make the SDK's
* own array unassignable at the call site.
*/
role: "user" | "assistant" | "system";
content: string | AnthropicGuardBlock[];
};
/**
* Structural view of one Gemini history entry, loose enough to accept both the
* native Vertex loop's `{ role, parts }` array and `@google/genai` contents
* without a cast at either call site.
*/
export type GeminiGuardContent = {
role: string;
parts: unknown[];
};
/** Tuning for {@link planLoopGuardReclaim}. */
export type LoopGuardPolicy = {
availableInputTokens: number;
/** System prompt + tool definitions — rides outside the message array. */
fixedOverheadTokens: number;
/** Fraction of the window at which the guard fires. */
thresholdRatio?: number;
/** Fraction of the window the guard reclaims down to once it fires. */
lowWaterRatio?: number;
/** Newest entries the guard must never modify. */
protectedTailCount?: number;
/** Observed/estimated token ratio, used to tighten both marks. */
calibration?: number;
};
/**
* One contiguous tool batch: the run of `tool_call` messages emitted by a
* single agent step, plus the run of `tool_result` messages that follows it.
* A step with parallel tool calls writes every call before any result, so the
* batch — not adjacency — is the unit that pairing and truncation operate on.
* `endIndex` is exclusive.
*/
export type RepairToolBatch = {
calls: ChatMessage[];
results: ChatMessage[];
endIndex: number;
};
/** Options for summarization prompt building. */
export type SummarizationPromptOptions = {
/**
* Whether this is an incremental update to an existing summary
*/
isIncremental: boolean;
/**
* The previous summary to merge with (required for incremental mode)
*/
previousSummary?: string;
/**
* List of files that have been read during the conversation
*/
filesRead?: string[];
/**
* List of files that have been modified during the conversation
*/
filesModified?: string[];
};
/** Configuration for tool output pruning (Stage 1). */
export type PruneConfig = {
protectTokens?: number;
minimumSavings?: number;
protectedTools?: string[];
provider?: string;
};
/** Result of tool output pruning (Stage 1). */
export type PruneResult = {
pruned: boolean;
messages: ChatMessage[];
tokensSaved: number;
};
/** Configuration for sliding window truncation (Stage 4). */
export type TruncationConfig = {
fraction?: number;
/** Current estimated tokens (enables adaptive mode) */
currentTokens?: number;
/** Target token budget (enables adaptive mode) */
targetTokens?: number;
/** Provider for token estimation (enables adaptive mode) */
provider?: string;
/** Buffer above required reduction (default: 0.15 = 15%) */
adaptiveBuffer?: number;
/** Maximum iterations for adaptive truncation (default: 3) */
maxIterations?: number;
};
/** Result of sliding window truncation (Stage 4). */
export type TruncationResult = {
truncated: boolean;
messages: ChatMessage[];
messagesRemoved: number;
};
/** Configuration for structured LLM summarization (Stage 3). */
export type SummarizeConfig = {
provider?: string;
model?: string;
keepRecentRatio?: number;
memoryConfig?: Partial<ConversationMemoryConfig>;
/** Target token budget — when set, split uses token counting instead of message count */
targetTokens?: number;
};
/** Result of structured LLM summarization (Stage 3). */
export type SummarizeResult = {
summarized: boolean;
messages: ChatMessage[];
summaryText?: string;
};
/** Result of file read deduplication (Stage 2). */
export type DeduplicationResult = {
deduplicated: boolean;
messages: ChatMessage[];
filesDeduped: number;
};
/** Options for FileSummarizationService. */
export type FileSummarizationServiceOptions = {
provider?: string;
model?: string;
};
/** Per-turn configuration for reclaiming a native V3 conversation. */
export type NativeGenerateGuardConfig = {
provider: string;
availableInputTokens: number;
getFixedOverheadTokens: () => number;
};
/** Tuning for the relevance stage of context compaction. */
export type ContextRelevanceOptions = {
/** Trailing messages never eligible for dropping. Default 6. */
protectRecent?: number;
/** Confidence required to drop a message. Default 0.6. */
minDropConfidence?: number;
/** Ceiling on the share of eligible messages one pass may drop. Default 0.5. */
maxDropRatio?: number;
/** Per-call timeout override for the decision request. */
timeoutMs?: number;
};
/** What the relevance stage concluded. */
export type ContextRelevanceResult = {
/** The surviving messages, in their original order. */
messages: ChatMessage[];
/** Indices into the ORIGINAL array that were dropped. */
droppedIndices: number[];
/** How many messages were asked about. */
askedCount: number;
/** How many came back with a usable, confident answer. */
answeredCount: number;
/** Resolved decision model id, for telemetry. */
model: string;
/** Round trip in milliseconds. */
latencyMs: number;
};
/**
* Injected dependencies for {@link ContextCompactor}.
*
* Deliberately separate from `CompactionConfig`: that type is spread into a
* `Required<CompactionConfig>` default, so a function member would need a
* default implementation, and the compactor must stay fully usable with no
* decision provider configured.
*/
export type ContextCompactorDeps = {
/** Fail-open decision caller. Omitted = the previous pipeline, exactly. */
decide?: DecisionCallerFn;
/** Tuning for the relevance stage. */
relevance?: ContextRelevanceOptions;
/** Tuning for the post-summarization quality gate. */
summaryGate?: {
timeoutMs?: number;
minConfidence?: number;
};
};
/** Per-call context handed to `ContextCompactor.compact`. */
export type ContextCompactRequest = {
/**
* The request being prepared. Without it the relevance stage cannot run —
* "is this message still needed?" is meaningless without knowing what for.
*/
currentRequest?: string;
};
export {};