@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
3,580 lines • 160 kB
TypeScript
import type { DecisionCallerOptions, DecisionLimitsQuery, DecisionLimitsReading, DecisionOptions, DecisionResult, AgentDefinition, AgentNetworkConfig, AgentRunOptions, AgentRunOutcome, AgentToolRegistrationOptions, IsolatedAgentDefinition, NetworkExecutionInput, NetworkExecutionOptions, NetworkExecutionResult, NetworkStreamChunk, WorkerInstanceOptions } from "./types/index.js";
import type { CompactionConfig, CompactionResult, ArtifactStore, SpanData, ObservabilityConfig, MetricsSummary, MCPToolAnnotations, TraceView, AuthenticatedContext, AuthProvider, JsonObject, NeuroLinkEvents, TypedEventEmitter, MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig, ChatMessage, ConversationSeedMessage, ConversationSeedOptions, RecordConversationTurnOptions, RecordConversationTurnResult, ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPServerHealth, ExternalMCPToolInfo, GenerateOptions, GenerateResult, ProviderStatus, TextGenerationOptions, TextGenerationResult, MCPExecutableTool, MCPServerInfo, MCPStatus, StreamOptions, StreamResult, ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions, BatchOperationResult, StreamGenerationEndContext, ToolRoutingServerDescriptor, ToolDedupConfig, ToolConfig, KnowledgeEngineStatus, ChecklistState, ArtifactPageRequest, BankArtifactOptions, BankedArtifactRef, DelegateCollectRequest, DelegateCollectResult, DelegateHandle, DelegateRegistrationOptions, DelegateSpawnOptions, BackgroundCommandHandle, BackgroundCommandOptions, BackgroundCommandOutputPage, BackgroundCommandPageRequest, BackgroundCommandPolicy, BackgroundCommandStatus, GitToolResult, GitToolsetOptions } from "./types/index.js";
import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
import type { RedisConversationMemoryManager } from "./core/redisConversationMemoryManager.js";
import { ExternalServerManager } from "./mcp/externalServerManager.js";
import { MCPToolRegistry } from "./mcp/toolRegistry.js";
import type { DynamicOptions } from "./types/index.js";
import { SkillsManager } from "./skills/skillsManager.js";
import type { CostAnalysisOptions, CostAnalysisResult, ProviderMetricsOptions, ProviderMetricsResult, TeamAnalyticsOptions, TeamAnalyticsResult } from "./types/index.js";
import { TaskManager } from "./tasks/taskManager.js";
import type { TranscribeOptions, TranscribeResult, TranscribeStreamEvent, TranscribeStreamOptions, SessionExport, SessionListItem } from "./types/index.js";
/**
* Curator P2-4 dedup (concurrency-safe): native providers emit
* `generation:end` on the shared SDK emitter. We attach a fresh
* mutable `dedupContext` object directly to the per-call
* `StreamOptions` (under `_streamDedupContext`) so each stream gets
* its own instance — concurrent streams have different option objects
* and therefore different contexts, so they cannot interfere.
*
* Native provider emit sites read `options._streamDedupContext` and
* flip `.providerEmitted = true` before emitting; the orchestration's
* finally block reads the same closed-over reference and skips its
* own emit when the flag is set.
*
* This avoids the AsyncLocalStorage approach which doesn't reliably
* propagate through async-generator yield boundaries when iteration
* happens from outside the original `run()` scope (e.g. when the
* consumer drives `for await of result.stream` after `sdk.stream(...)`
* returns).
*/
export declare const STREAM_DEDUP_CONTEXT_KEY: "_streamDedupContext";
/**
* Native providers call this from their `generation:end` emit sites,
* passing the same `options` object they received. Safe no-op when
* the field isn't set.
*/
export declare function markStreamProviderEmittedGenerationEnd(options: {
_streamDedupContext?: StreamGenerationEndContext;
} | undefined): void;
/**
* Symbol-based brand for cross-module identification without circular imports.
*
* Provider constructors receive `sdk?: unknown` (the factory layer's
* contract). Rather than duck-typing via `"getInMemoryServers" in sdk`,
* use `isNeuroLink(value)` from this module to do a brand check —
* survives minification AND doesn't rely on method-name stability.
*/
export declare const NEUROLINK_BRAND: unique symbol;
/**
* Type-guard for opaque values that should be a {@link NeuroLink} instance.
*
* Designed for the provider-factory boundary where TS can't carry the type
* through `UnknownRecord` without forcing every caller into a circular
* dependency. Cheap to call and unaffected by minification.
*/
export declare function isNeuroLink(value: unknown): value is NeuroLink;
export declare class NeuroLink {
private mcpInitialized;
private mcpSkipped;
private mcpInitPromise;
private emitter;
/** Request ownership for conversation capture; unscoped emitter events are ignored. */
private readonly conversationEventContext;
/**
* Process-unique id used to attribute this instance's log events. The SDK
* entry points run their bodies inside `logger.runInInstanceScope(this.logInstanceId, …)`,
* which is what lets a worker's log bridge receive only the worker's own
* events instead of everything in the process.
*/
private readonly logInstanceId;
/**
* Detach callbacks for scoped log bridges of workers this instance created
* via {@link createWorkerInstance} with `onLog`, that have not yet been
* removed by the worker's own `dispose()`. The scoped-emitter registry
* lives on the process-global logger, keyed by the WORKER's id — not this
* host's — so it survives this host being garbage collected. If a caller
* disposes the host but forgets an individual worker (the host is the
* long-lived object in most delegation patterns; the worker the
* short-lived one), the bridge — and everything its `onLog` closes over —
* would otherwise stay registered for the life of the process. `dispose()`
* sweeps whatever is still here so host cleanup is sufficient on its own,
* matching the pre-scoped-routing behavior where a worker's forward
* listener lived on the host's own emitter and was removed by
* `this.emitter.removeAllListeners()`.
*/
private readonly ownedWorkerLogBridgeDetachers;
private _taskManager?;
private _taskManagerConfig?;
private toolRegistry;
private autoDiscoveredServerInfos;
private externalServerManager;
private toolCache;
private readonly toolCacheDuration;
private modelAliasConfig?;
private lastCompactionMessageCount;
/** Extract sessionId from options context for compaction watermark keying */
private getCompactionSessionId;
private mcpToolResultCache?;
private mcpToolRouter?;
private mcpToolBatcher?;
private mcpEnhancedDiscovery?;
private mcpToolMiddlewares;
/** Artifact store for externalized MCP tool outputs and banked payloads. */
private mcpArtifactStore?;
/** `artifacts` constructor config — consulted whenever a store is created. */
private artifactsConfig?;
/**
* True when this instance built `mcpArtifactStore` itself (local or Redis
* from config / STORAGE_TYPE). NeuroLink closes only stores it built — on
* replacement and on shutdown; a store handed in through `artifacts.store`
* or `setArtifactStore()` is the caller's to close.
*/
private ownsArtifactStore;
/** Normalizer settings, kept so `setArtifactStore()` can rebuild it. */
private mcpOutputNormalizerConfig?;
private _disableToolCacheForCurrentRequest;
/**
* (toolName + args) keys already served during the CURRENT request.
* A repeat occurrence within one request bypasses the tool-result cache:
* when the model deliberately re-calls a tool with identical args in the
* same turn (a counter, a poll, "check status again"), it wants fresh
* state — serving the memoized first result silently freezes stateful
* tools (observed live: a 5-round counter loop executed once). Cross-
* request dedup — BZ-664's actual goal — is untouched: the first
* occurrence in a request may still be served from cache. Request-scoped
* like _disableToolCacheForCurrentRequest above (assigned a fresh Set at
* request start so `preservingTurnState`'s save/restore-by-reference works).
*/
private _toolCacheKeysServedThisRequest;
/** True only while a generate()/stream() turn is executing — the
* repeat-call cache bypass applies inside a turn; direct executeTool
* calls keep full BZ-664 cache semantics. */
private _generationTurnActive;
private mcpEnhancementsConfig?;
private toolCircuitBreakers;
private toolExecutionMetrics;
private currentStreamToolExecutions;
private toolExecutionHistory;
private activeToolExecutions;
/**
* Helper method to emit tool end event in a consistent way
* Used by executeTool in both success and error paths
* @param toolName - Name of the tool
* @param startTime - Timestamp when tool execution started
* @param success - Whether the tool execution was successful
* @param result - The result of the tool execution (optional)
* @param error - The error if execution failed (optional)
*/
private emitToolEndEvent;
conversationMemory?: ConversationMemoryManager | RedisConversationMemoryManager | null;
private conversationMemoryNeedsInit;
private conversationMemoryConfig?;
private toolRoutingConfig?;
private toolRoutingCacheInstance?;
private toolRoutingVectorCache?;
private knowledgeGroundingEngine?;
private toolDedupConfig?;
private toolsConfig?;
/** Session-scoped pins of tools discovered via search_tools (tools.discovery mode). */
private discoveryPins;
private enableOrchestration;
private authProvider?;
private pendingAuthConfig?;
private authInitPromise?;
private credentials?;
private readonly fallbackConfig;
private readonly modelPool;
private readonly requestRouter;
private readonly classifierRouter;
private readonly contextRelevanceOptions;
private readonly decisionHooks;
/**
* Merge instance-level credentials with per-call credentials.
*
* Semantics: **deep merge at the provider level.** For each provider key
* present in both `this.credentials` and `callCredentials`, the per-call
* fields are merged ON TOP of the instance-level fields, so fields not
* mentioned in the per-call slice are preserved.
*
* Example:
* ```
* instance: { openai: { apiKey: "key1", baseURL: "url1" } }
* per-call: { openai: { apiKey: "key2" } }
* merged: { openai: { apiKey: "key2", baseURL: "url1" } } // baseURL preserved
* ```
*
* Providers present only in one source are carried through unchanged.
* Unrelated providers (not overridden in callCredentials) are carried through
* from instance credentials unchanged.
*/
private resolveCredentials;
private hitlManager?;
private _sessionCostUsd;
private fileRegistry;
private cachedFileTools;
private memoryInstance?;
private memorySDKConfig?;
/**
* Set when the instance-level condensation prompt failed validation at
* construction (see initializeConversationMemory). Memory stays off for
* the instance's lifetime; the reason is what the warning already said.
*/
private memoryDisabledReason?;
/**
* Dedicated child instance Hippocampus condenses through. Built on first
* condensation, never at construction — so a read-only host, or one whose
* memory never activates, never pays for a second NeuroLink.
*/
private memoryCondenser?;
/**
* Set by `releaseMemoryCondenser()`, i.e. by `shutdown()` / `dispose()`,
* once the writes already scheduled have been drained. A memory write
* scheduled past that point must skip, and the condenser must never be
* rebuilt: a child resurrected after release would outlive its host,
* unreleased, and — after `dispose()` wiped the credentials — condense
* with none.
*/
private memoryCondenserReleased;
/**
* Memory writes scheduled and not yet settled. `releaseMemoryCondenser()`
* awaits them (bounded) before it releases anything, so
* `await generate(); await shutdown();` — a serverless handler, a
* per-request instance, a one-shot CLI run — still stores its last turn.
*/
private readonly pendingMemoryWrites;
/** Prompt templates already warned about, so a per-call template warns once. */
private readonly warnedCondensationPrompts;
/** `decisionHooks.hookTimeoutMs` once validated; see `decisionHookTimeout`. */
private decisionHookTimeoutMs?;
private skillsManagerInstance?;
private skillsConfig?;
private setLangfuseContextFromOptions;
private createMetricsTraceContext;
private enforceSessionBudget;
private assertInputText;
private applyAuthenticatedRequestContext;
private applyGenerateLifecycleMiddleware;
private applyStreamLifecycleMiddleware;
private initializeMemoryConfig;
/**
* A `generate()`-only handle whose backing NeuroLink is constructed on the
* first condensation call, not when memory is configured or first read.
*/
private lazyMemoryCondenserInstance;
/**
* Release the lazily created condenser with its host. Its own
* `shutdown()` is a no-op when condensation never ran; when it did, the
* child's memory is disabled and it holds no MCP servers, so this is
* cheap — the point is that the host's lifecycle owns the child's.
*
* The writes already scheduled are drained FIRST, bounded by the write
* timeout: a turn's memory write is deferred to `setImmediate`, so
* `await generate(); await shutdown();` would otherwise never store the
* turn. Only then is the host marked released, so a write scheduled after
* that point skips instead of rebuilding the child.
*/
private releaseMemoryCondenser;
/**
* Wait for every scheduled memory write to settle, including writes
* scheduled while waiting, within one `MEMORY_WRITE_TIMEOUT_MS` budget.
* A write that outlives the budget is left in flight and warned about —
* it will skip or fail on its own once the condenser is gone — rather
* than hold shutdown open indefinitely.
*/
private drainPendingMemoryWrites;
private warnMemoryWritesLeftInFlight;
/**
* The child instance Hippocampus condenses through, or null once the host
* has been released.
*
* Deliberately NOT `this`: routing condensation through the parent would
* run its MCP init, skills and middleware, emit `generation:*` on the
* parent emitter and add the condensation call's cost to
* `_sessionCostUsd`. The child inherits `credentials` (or the explicit
* `memory.neurolink.credentials`) so condensation no longer depends on
* provider keys in `process.env`, has memory disabled so it can never
* recurse into a memory write of its own, and — like createWorkerInstance —
* joins the host tracer without a second Langfuse exporter.
*/
private getMemoryCondenser;
/**
* Lazy initialization for memory — called during generate/stream.
*/
private ensureMemoryReady;
/**
* Context storage for tool execution
* This context will be merged with any runtime context passed by the AI model
*/
private toolExecutionContext?;
/**
* Set when registerAgentTool() has registered at least one delegation
* tool — gates the per-turn delegation scope in generate().
*/
private hasAgentTools;
/**
* Tools registered with `cacheable: false` — their results are never served
* from the tool-result cache. A tool whose answer depends on live state (a
* checklist, a work queue) would otherwise replay its first answer for the
* whole TTL to every caller passing the same arguments.
*/
private readonly uncacheableTools;
/** Set once registerTaskTools() has registered the checklist toolset. */
private hasTaskChecklistTools;
/** Set once registerDelegationTools() has registered the delegation toolset. */
private hasBackgroundDelegationTools;
/** Set once registerBackgroundCommandTools() has registered the command toolset. */
private hasBackgroundCommandTools;
/** Set once registerGitTools() has registered the read-only git toolset. */
private hasGitTools;
/**
* Set once `retrieve_context` has been registered. The constructor skips
* registration when neither Redis nor an artifact store exists; banking can
* create a store later, and this keeps that second attempt from
* re-registering the tool on instances that already have it.
*/
private retrieveContextRegistered;
/**
* Creates a new NeuroLink instance for AI text generation with MCP tool integration.
*
* @param config - Optional configuration object
* @param config.conversationMemory - Configuration for conversation memory features
* @param config.conversationMemory.enabled - Whether to enable conversation memory (default: false)
* @param config.conversationMemory.maxSessions - Maximum number of concurrent sessions (default: 100)
* @param config.conversationMemory.maxTurnsPerSession - Maximum conversation turns per session (default: 50)
* @param config.enableOrchestration - Whether to enable smart model orchestration (default: false)
* @param config.hitl - Configuration for Human-in-the-Loop safety features
* @param config.hitl.enabled - Whether to enable HITL tool confirmation (default: false)
* @param config.hitl.dangerousActions - Keywords that trigger confirmation (default: ['delete', 'remove', 'drop'])
* @param config.hitl.timeout - Confirmation timeout in milliseconds (default: 30000)
* @param config.hitl.allowArgumentModification - Allow users to modify tool parameters (default: true)
* @param config.toolRegistry - Optional tool registry instance for advanced use cases (default: new MCPToolRegistry())
*
* @example
* ```typescript
* // Basic usage
* const neurolink = new NeuroLink();
*
* // With conversation memory
* const neurolink = new NeuroLink({
* conversationMemory: {
* enabled: true,
* maxSessions: 50,
* maxTurnsPerSession: 20
* }
* });
*
* // With orchestration enabled
* const neurolink = new NeuroLink({
* enableOrchestration: true
* });
*
* // With HITL safety features
* const neurolink = new NeuroLink({
* hitl: {
* enabled: true,
* dangerousActions: ['delete', 'remove', 'drop', 'truncate'],
* timeout: 30000,
* allowArgumentModification: true
* }
* });
* ```
*
* @throws {Error} When provider registry setup fails
* @throws {Error} When conversation memory initialization fails (if enabled)
* @throws {Error} When external server manager initialization fails
* @throws {Error} When HITL configuration is invalid (if enabled)
*/
private observabilityConfig?;
private telemetryHeld;
private metricsAggregator;
private analyticsService;
/**
* Per-request metrics trace context backed by AsyncLocalStorage.
* Safe for concurrent requests on the same SDK instance.
* Context is set via metricsTraceContextStorage.run() in generate/stream.
*/
private get _metricsTraceContext();
constructor(config?: NeurolinkConstructorConfig);
/**
* TaskManager — scheduled and self-running tasks.
* Lazy-initialized on first access. Configurable via constructor `tasks` option.
* The actual async initialization (Redis connect, backend start) happens
* lazily inside TaskManager on first operation.
*/
get tasks(): TaskManager;
/**
* Initialize provider registry with security settings
*/
private initializeProviderRegistry;
/**
* Initialize conversation memory if enabled
*/
private initializeConversationMemory;
/**
* Validate the condensation prompt template memory will condense with —
* the instance `prompt`, else `HC_CONDENSATION_PROMPT` (which Hippocampus
* reads when no config prompt is set). Hippocampus substitutes
* placeholders with `replaceAll` and requires none, so a template missing
* `{{NEW_CONTENT}}` yields a memory that never grows and one missing
* `{{OLD_MEMORY}}` overwrites the summary on every turn — both silently.
*
* Default is disable-and-log, matching every other memory failure; the
* log line is `error` because it is the only signal an operator gets that
* memory is off. With `memory.strictPrompt` the constructor throws instead.
*
* An empty or whitespace-only template counts as unset — Hippocampus
* resolves `config.prompt || HC_CONDENSATION_PROMPT || built-in`, so `""`
* means "use the next one down", never "a template with no placeholders".
*/
private validateInstanceCondensationPrompt;
/**
* Initialize HITL (Human-in-the-Loop) if enabled
*/
private initializeHITL;
/**
* Initialize MCP enhancement modules (cache, router, batcher, discovery).
* Wires standalone MCP modules into the core SDK execution path.
*/
private initializeMCPEnhancements;
/**
* Register file reference tools with the MCP tool registry.
*
* Creates file access tools (list_attached_files, read_file_section,
* search_in_file, get_file_preview) bound to the FileReferenceRegistry
* and registers them as direct tools so they're available to LLMs.
*/
private registerFileTools;
/**
* Register SCHEDULER task tools bound to a TaskManager instance (task_create,
* task_list, … — scheduled/self-running jobs). Distinct from the agent task
* CHECKLIST (registerTaskTools() / tasks_create): different tools, different
* state, different purpose.
* Follows the same factory + registry pattern as registerFileTools().
* Called when TaskManager is created (eagerly or lazily via the `tasks` getter).
*/
private registerSchedulerTaskTools;
/**
* Register memory retrieval tools that allow the AI to access
* conversation history, including full tool outputs.
* Only registered when Redis conversation memory is active.
*/
private registerMemoryRetrievalTools;
/**
* Lazy initialization for the skills subsystem — mirrors ensureMemoryReady().
* Returns null (and stays null) when skills are not configured or the
* store failed to initialize; read paths fail open on that null.
*/
private ensureSkillsReady;
/**
* Register the built-in skill tools (list_skills, plus
* mutation tools when allowMutations is set). Follows the
* registerMemoryRetrievalTools() pattern: registered via registerTool()
* so they land in the "user-defined" category that reaches the LLM tool
* schema, with the manager resolved lazily at execution time.
*/
private registerSkillTools;
/**
* Skills augmentation for one generate()/stream() call:
* 1. Discovery — surface the skills listing per the resolved mode:
* embedded in the use_skill tool description ("tool", default) or
* appended to the system prompt ("system-prompt").
* 2. Per-call tools — inject use_skill + read_skill_resource into
* options.tools (the RAG-tool pattern; same-name entries shadow
* registered tools) with the sessionId closure-bound so activations
* pin to the session.
* 3. Preload — activate host-requested skills up front, injecting their
* instructions into this call's system prompt and pinning them.
* Fails open: any error leaves the call untouched.
*/
private applySkillsAugmentation;
/** Session id for skill activation tracking, from the call context. */
private resolveSkillSessionId;
/** User id for skill-session hydration (Redis keys sessions by userId). */
private resolveSkillUserId;
/**
* Activate host-requested skills before the model runs: instructions go
* into this call's system prompt; when persisting, the activation is
* pinned so later turns replay it from history instead. Unknown or
* already-active names are skipped with a warn/debug log (fail-open).
*/
private preloadSkills;
/**
* Pinned skill messages recorded during this turn, ready for
* StoreConversationTurnOptions.skillMessages. Empty when skills are off
* or nothing was activated.
*/
private drainPendingSkillMessages;
/**
* Programmatic access to the skills subsystem (search/list/get/mutations).
* Returns null when skills are not configured or failed to initialize.
*/
getSkillsManager(): SkillsManager | null;
/** Format memory context for prompt inclusion */
private formatMemoryContext;
/**
* Format memory context from multiple users into a labeled block.
*/
private formatMultiUserMemoryContext;
/**
* Determine whether memory should be read (retrieved) for this call.
* Respects both the global memory SDK config and per-call overrides.
*/
private shouldReadMemory;
/**
* Determine whether memory should be written (stored) for this call.
* Respects both the global memory SDK config and per-call overrides.
*/
private shouldWriteMemory;
/**
* Retrieve condensed memory for a user (and optionally additional users).
* Returns the input text enhanced with memory context, or unchanged if no memory.
*/
private retrieveMemory;
/**
* Build the `add()` options for one memory owner. A per-owner prompt that
* fails template validation is dropped (warned once per distinct template)
* so that owner condenses with the instance prompt instead — Hippocampus
* resolves `options.prompt || this.prompt`, so omitting it is the fallback.
* An empty or whitespace-only prompt counts as unset and is omitted the
* same way, unvalidated: `""` already falls through in Hippocampus, but
* `" "` is truthy and would be used literally.
*/
private resolveMemoryAddOptions;
/**
* Store a conversation turn in memory (non-blocking).
* Calls add(userId, content) which internally condenses old + new via LLM.
* Supports additional users with per-user prompt and maxWords overrides.
*
* The write hooks (`shouldWrite`, `onBeforeStore`; per-call wins over
* instance) run inside the deferred closure so an async hook never sits on
* the response path. A `false` / `null` verdict, a non-string transform or
* a thrown hook skips every `add()` for the turn — a missed memory is
* recoverable, a polluted condensed summary is not.
*/
private storeMemoryInBackground;
/**
* Set up HITL event forwarding to main emitter
*/
private setupHITLEventForwarding;
/**
* Initialize external server manager with event handlers
*/
private initializeExternalServerManager;
/**
* Setup event handlers for external server manager
*/
private setupExternalServerEventHandlers;
private holdTelemetry;
private releaseTelemetryHold;
/**
* Log constructor completion with final state summary
*/
private logConstructorComplete;
/**
* Initialize MCP registry with enhanced error handling and resource cleanup
* Uses isolated async context to prevent hanging
*/
private initializeMCP;
/**
* Actual one-shot MCP initialization logic. Called at most once per
* NeuroLink instance lifetime (unless cleanup() resets the flag).
*/
private performMCPInitializationOnce;
/**
* Import performance manager with error handling
*/
private importPerformanceManager;
/**
* Perform main MCP initialization logic
*/
private performMCPInitialization;
/**
* Initialize tool registry with timeout protection
*/
private initializeToolRegistryInternal;
/**
* Initialize provider registry
*/
private initializeProviderRegistryInternal;
/**
* Register direct tools server
*/
private registerDirectToolsServerInternal;
/**
* Load MCP configuration from .mcp-config.json with parallel loading for improved performance
*/
private loadMCPConfigurationInternal;
/**
* Log MCP initialization completion
*/
private logMCPInitComplete;
/**
* Apply orchestration to determine optimal provider and model
* @param options - Original GenerateOptions
* @returns Modified options with orchestrated provider marked in context, or empty object if validation fails
*/
private applyOrchestration;
/**
* Apply orchestration to determine optimal provider and model for streaming
* @param options - Original StreamOptions
* @returns Modified options with orchestrated provider marked in context, or empty object if validation fails
*/
private applyStreamOrchestration;
/**
* Read a nonempty orchestration preference only when provider is absent/auto.
* An explicit provider always takes precedence over incoming or generated
* context; empty, non-string and auto preferences are ignored.
*/
private getOrchestratedPreferredProvider;
/**
* MAIN ENTRY POINT: Enhanced generate method with new function signature
* Replaces both generateText and legacy methods
*/
/**
* Extracts the original prompt text from the provided input.
* If a string is provided, it returns the string directly.
* If a GenerateOptions object is provided, it returns the input text from the object.
* @param optionsOrPrompt The prompt input, either as a string or a GenerateOptions object.
* @returns The original prompt text as a string.
*/
private _extractOriginalPrompt;
/**
* Generate AI content using the best available provider with MCP tool integration.
* This is the primary method for text generation with full feature support.
*
* @param optionsOrPrompt - Either a string prompt or a comprehensive GenerateOptions object
* @param optionsOrPrompt.input - Input configuration object
* @param optionsOrPrompt.input.text - The text prompt to send to the AI (required)
* @param optionsOrPrompt.provider - AI provider to use ('auto', 'openai', 'anthropic', etc.)
* @param optionsOrPrompt.model - Specific model to use (e.g., 'gpt-4', 'claude-3-opus')
* @param optionsOrPrompt.temperature - Randomness in response (0.0 = deterministic, 2.0 = very random)
* @param optionsOrPrompt.maxTokens - Maximum tokens in response
* @param optionsOrPrompt.systemPrompt - System message to set AI behavior
* @param optionsOrPrompt.disableTools - Whether to disable MCP tool usage
* @param optionsOrPrompt.enableAnalytics - Whether to include usage analytics
* @param optionsOrPrompt.enableEvaluation - Whether to include response quality evaluation
* @param optionsOrPrompt.context - Additional context for the request
* @param optionsOrPrompt.evaluationDomain - Domain for specialized evaluation
* @param optionsOrPrompt.toolUsageContext - Context for tool usage decisions
*
* @returns Promise resolving to GenerateResult with content, usage data, and optional analytics
*
* @example
* ```typescript
* // Simple usage with string prompt
* const result = await neurolink.generate("What is artificial intelligence?");
* console.log(result.content);
*
* // Advanced usage with options
* const result = await neurolink.generate({
* input: { text: "Explain quantum computing" },
* provider: "openai",
* model: "gpt-4",
* temperature: 0.7,
* maxTokens: 500,
* enableAnalytics: true,
* enableEvaluation: true,
* context: { domain: "science", level: "intermediate" }
* });
*
* // Access analytics and evaluation data
* console.log(result.analytics?.usage);
* console.log(result.evaluation?.relevance);
* ```
*
* @throws {Error} When input text is missing or invalid
* @throws {Error} When all providers fail to generate content
* @throws {Error} When conversation memory operations fail (if enabled)
*/
/**
* Get observability configuration
*/
getObservabilityConfig(): ObservabilityConfig | undefined;
/**
* @description Reports whether any tracing backend is active.
* @returns {boolean} `true` when OpenTelemetry is initialized or a Langfuse exporter is running.
*/
isTelemetryEnabled(): boolean;
/**
* Get comprehensive telemetry status including Langfuse and OpenTelemetry state
*/
getTelemetryStatus(): {
enabled: boolean;
langfuse?: {
enabled: boolean;
baseUrl?: string;
environment?: string;
};
openTelemetry?: {
enabled: boolean;
endpoint?: string;
serviceName?: string;
};
};
/**
* Get aggregated observability metrics (latency, tokens, cost, success rate)
*/
getMetrics(): MetricsSummary;
/**
* Get all recorded spans
*/
getSpans(): SpanData[];
/**
* Get traces (spans grouped by traceId with parent-child hierarchy)
*/
getTraces(): TraceView[];
/**
* Reset all collected metrics and spans
*/
resetMetrics(): void;
/**
* Record a span for metrics tracking
*/
recordMetricsSpan(span: SpanData): void;
/**
* Get provider metrics analysis
* Retrieves aggregated performance, token usage, latency, and success rates per provider.
*
* @param options - Filtering options
* @returns Comprehensive provider metrics result
*/
getProviderMetrics(options?: ProviderMetricsOptions): Promise<ProviderMetricsResult>;
/**
* Get cost analysis breakdown
* Analyzes AI generation costs across requested groups and provides future projections.
*
* @param options - Cost configuration options
* @returns Detailed cost analysis breakdown
*/
getCostAnalysis(options?: CostAnalysisOptions): Promise<CostAnalysisResult>;
/**
* Get team-wide usage analytics
* Retrieves request counts, unique active users, provider breakdown, and quality scoring.
*
* @param options - Team query options
* @returns Comprehensive team analytics report
*/
getTeamAnalytics(options?: TeamAnalyticsOptions): Promise<TeamAnalyticsResult>;
/**
* Record a memory operation span to both instance and global metrics aggregators.
* This ensures memory spans are visible via sdk.getSpans() and getMetricsAggregator().getSpans().
*/
private recordMemorySpan;
/**
* Public method to initialize Langfuse observability
* This method can be called externally to ensure Langfuse is properly initialized
*/
initializeLangfuseObservability(): Promise<void>;
/**
* Gracefully shutdown NeuroLink and all MCP connections
*/
shutdown(): Promise<void>;
/**
* Fire-and-forget analytics tracking with rejection logging.
*/
private enqueueAnalyticsTrackRequest;
/**
* Estimate cost using the canonical SDK pricing table (same source as
* AnalyticsService.trackRequest) — avoids diverging TokenTracker rates.
*/
private estimateCostFromUsage;
/**
* Handle generation:end: records advanced analytics for every generation
* and the metrics span for a non-streamed one. A streamed generation gets
* its span from stream:complete / stream:error instead.
*/
private handleGenerationEndMetrics;
/**
* Pipeline B span recording for stream:complete.
* Advanced analytics are recorded from generation:end only (avoids duplicates
* when both stream:complete and generation:end fire for the same request).
*/
private handleStreamCompleteMetrics;
private handleToolEndMetrics;
/**
* Pipeline B span recording for stream:error.
* Analytics for failed streams come from generation:end (success: false).
*/
private handleStreamErrorMetrics;
/**
* Initialize event listeners that feed span data to MetricsAggregator.
* Listens to generation:end, stream:complete, and tool:end events.
*/
private initializeMetricsListeners;
/**
* Generate AI response with comprehensive feature support.
*
* Primary method for AI generation with support for all NeuroLink features:
* - Multi-provider support
* - MCP tool integration
* - Structured JSON output with Zod schemas
* - Conversation memory (Redis or in-memory)
* - HITL security workflows
* - Middleware execution
* - Multimodal inputs (images, PDFs, CSV)
*
* @category Generation
*
* @param optionsOrPrompt - Generation options or simple text prompt
* @param optionsOrPrompt.input - Input text and optional files
* @param optionsOrPrompt.provider - AI provider name (e.g., 'vertex', 'openai', 'anthropic')
* @param optionsOrPrompt.model - Model name to use
* @param optionsOrPrompt.tools - MCP tools to enable for this generation
* @param optionsOrPrompt.schema - Zod schema for structured output validation
* @param optionsOrPrompt.temperature - Sampling temperature (0-2, default: 1.0)
* @param optionsOrPrompt.maxTokens - Maximum tokens to generate
* @param optionsOrPrompt.thinkingConfig - Extended thinking configuration (thinkingLevel: 'minimal'|'low'|'medium'|'high')
* @param optionsOrPrompt.context - Context with conversationId and userId for memory
* @param optionsOrPrompt.useKnowledgeGrounding - Whether to use the instance's configured knowledge for this call
* @returns Promise resolving to generation result with content and metadata
*
* @example Basic text generation
* ```typescript
* const result = await neurolink.generate({
* input: { text: 'Explain quantum computing' }
* });
* console.log(result.content);
* ```
*
* @example With specific provider
* ```typescript
* const result = await neurolink.generate({
* input: { text: 'Write a poem' },
* provider: 'anthropic',
* model: 'claude-3-opus'
* });
* ```
*
* @example With MCP tools
* ```typescript
* const result = await neurolink.generate({
* input: { text: 'Read README.md and summarize it' },
* tools: ['readFile']
* });
* ```
*
* @example With structured output
* ```typescript
* import { z } from 'zod';
*
* const schema = z.object({
* name: z.string(),
* age: z.number(),
* city: z.string()
* });
*
* const result = await neurolink.generate({
* input: { text: 'Extract person info: John is 30 years old from NYC' },
* schema: schema
* });
* // result.structuredData is type-safe!
* ```
*
* @example With conversation memory
* ```typescript
* const result = await neurolink.generate({
* input: { text: 'What did we discuss earlier?' },
* context: {
* conversationId: 'conv-123',
* userId: 'user-456'
* }
* });
* ```
*
* @example With multimodal input
* ```typescript
* const result = await neurolink.generate({
* input: {
* text: 'Describe this image',
* images: ['/path/to/image.jpg']
* },
* provider: 'vertex'
* });
* ```
*
* @throws {Error} When input text is missing or invalid
* @throws {Error} When all providers fail to generate content
* @throws {Error} When structured output validation fails
* @throws {Error} When HITL approval is denied
*
* @see {@link GenerateOptions} for all available options
* @see {@link GenerateResult} for result structure
* @see {@link stream} for streaming generation
* @since 1.0.0
*/
generate(optionsOrPrompt: GenerateOptions | DynamicOptions | string): Promise<GenerateResult>;
/**
* Body of {@link generate}, always entered through the instance log scope
* so every log call beneath it is attributable to this instance.
*/
private generateInInstanceScope;
private runGenerateTurn;
private fireConsumerOnErrorIfNotFired;
/**
* Curator P2-3: wraps a generate/stream call with the fallback
* orchestration (`providerFallback` callback + `modelChain` walker).
*
* On a qualifying error from the inner call:
* 1. Resolve the effective callback (per-call > instance > synthesised
* from modelChain) and the effective chain (per-call > instance).
* 2. Walk attempts: invoke callback (or pop next chain entry) → emit
* `model.fallback` event → re-call inner with the new {provider,
* model}.
* 3. Stop on first success, on a callback returning null, or after
* exhausting the chain (throw the most recent error).
*
* What qualifies depends on how fallback was configured: an EXPLICIT
* `providerFallback` callback is consulted for any error except client
* aborts (the callback owns the decision — it receives the error
* unmodified and can return null to bubble), while modelChain-only
* configs keep the narrow model-access-denied gate.
*/
private runWithFallbackOrchestration;
private attemptInner;
private executeGenerateWithMetricsContext;
private executeGenerateRequest;
private prepareGenerateRequest;
private maybeHandleEarlyGenerateResult;
private runStandardGenerateRequest;
private maybeApplyGenerateOrchestration;
/**
* Applies the host-configured `requestRouter` to `options` in place.
*
* The router is skipped when:
* - no `requestRouter` is configured on this instance, or
* - a valid orchestration preference already owns the provider/model pair, or
* - the caller explicitly set both `options.provider` AND `options.model`
* (we only skip the router if BOTH are set; a caller setting only one
* still lets the router fill in the other field).
*
* Fails open: any router error is logged at WARN level and the call
* continues with the original options unmodified.
*
* @param options — the mutable options object (generate or stream).
* @param promptText — the text prompt used to build the RouterInputContext.
* @param hasTools — true when at least one tool is available for this call.
* @param requiresVision — true when the call includes image attachments.
* @param thinkingLevel — optional thinking level string from the call options.
*/
private applyRequestRouter;
/**
* Dependencies handed to every `ContextCompactor`.
*
* `siteDecide` returns null whenever no decision provider is configured or
* the call fails, so passing this unconditionally is safe: the compactor
* runs its previous pipeline unchanged and pays nothing for the option.
* `outer` is the request the compaction runs for — its credentials, abort
* signal and ids — so both decision stages go to the caller's account.
*/
private contextCompactorDeps;
/**
* What a decide site inherits from the request it runs inside. Read off the
* request options so every site — routing, tool routing, both compaction
* stages — forwards the same fields the same way. The request id is the
* documented top-level `requestId` first, then `context.requestId`.
*/
private decisionSiteContext;
/**
* Applies the host-configured `classifierRouter` to `options` in place.
*
* Classifies the request by difficulty and selects a provider/model from the
* configured base pool (cheaper/faster for easy tasks, more capable for hard
* ones), and optionally narrows the tool set via `toolFilter`/`excludeTools`.
*
* Skipped when: no router is configured; the inbound call is the classifier's
* own generate() (marked `__classifierRouted`); the caller pinned BOTH
* provider and model; or a ModelPool is configured (the pool owns selection).
* Fails open — any error leaves options unchanged.
*/
private applyClassifierRouting;
private prepareGenerateAugmentations;
private buildGenerateTextOptions;
/**
* Provider-agnostic JSON recovery for schema requests. Structured-output
* enforcement makes valid JSON the overwhelming case; for every other
* provider path — including generate() overrides (Vertex, Anthropic,
* Bedrock, Google AI Studio) — object/array roots are recovered here via
* balanced-scan + jsonrepair and scalar JSON roots via plain JSON.parse,
* with the parsed value exposed as `structuredData`. If nothing JSON-shaped
* is recoverable (pure prose), the raw text is returned, `structuredData`
* stays undefined, and a WARN makes the case observable.
*
* Mutates `textResult` in place, and must run BEFORE the end-of-generation
* emits so event consumers see the same content/structuredData the caller
* receives.
*/
private recoverStructuredData;
private finalizeGenerateRequestResult;
private emitGenerateErrorEvent;
/**
* Schedule non-blocking memory storage after generate completes.
*/
private scheduleGenerateMemoryStorage;
/**
* Handle PPT generation mode
*/
private generateWithPPT;
/**
* Dispatch a music-generation request to the registered music handler
* for the provider named in `options.output.music.provider`.
*/
private generateWithMusic;
/**
* Dispatch an avatar (lip-sync) request to the registered avatar handler
* for the provider named in `options.output.avatar.provider`.
*/
private generateWithAvatar;
/**
* Generate with workflow engine integration
* Returns both original and processed responses for AB testing
*/
private generateWithWorkflow;
/**
* Stream with workflow engine integration
* Progressive streaming: yields preliminary response (first model) then final synthesis
*/
private streamWithWorkflow;
/**
* BACKWARD COMPATIBILITY: Legacy generateText method
* Internally calls generate() and converts result format
*/
generateText(options: TextGenerationOptions): Promise<TextGenerationResult>;
/**
* Body of {@link generateText}, always entered through the instance log scope.
*/
private generateTextInInstanceScope;
/**
* REDESIGNED INTERNAL GENERATION - NO CIRCULAR DEPENDENCIES
*
* This method implements a clean fallback chain:
* 1. Initialize conversation memory if enabled
* 2. Inject conversation history into prompt
* 3. Try MCP-enhanced generation if available
* 4. Fall back to direct provider generation
* 5. Store conversation turn for future context
*/
private generateTextInternal;
private executeGenerateTextInternalWithSpan;
private executeGenerateTextInternalInEventScope;
private initializeGenerateTextInternalContext;
private runGenerateTextInternalFlow;
private captureOriginalConversationMessagesForRecovery;
private finalizeGenerateTextInternalResult;
private handleGenerateTextInternalFailure;
private tryRecoverGenerateTextOverflow;
/**
* Log generateTextInternal start with comprehensive analysis
*/
private logGenerateTextInternalStart;
/**
* Emit generation start events
*/
private emitGenerationStartEvents;
/**
* Initialize conversation memory for generation
* Lazily initializes memory if needed from constructor flags
*/
private initializeConversationMemoryForGeneration;
/**
* Attempt MCP generation with retry logic
*/
private attemptMCPGeneration;
/**
* Non-retryable tool-error predicate shared by `performMCPGenerationRetries`
* and `tryMCPGeneration`. Returns `true` when the error indicates the model
* hallucinated a tool name or sent invalid arguments — retrying would
* re-trigger the same deterministic failure with the same tool set.
*
* Belt-and-suspenders: prefers class-identity `.isInstance()` (stable across
* Phase 5+ native substitution) but falls back to `error.name` and message
* substring checks so cross-module-boundary errors (Jest, bundlers, wrapped
* errors) are still caught. The name fallback covers both
* `AI_InvalidToolInputError` (current v5/v6 SDK) and the legacy
* `AI_InvalidToolArgumentsError` (v4).
*/
private static isNonRetryableToolError;
/**
* Perform MCP generation with retry logic
*/
private performMCPGenerationRetries;
/**
* Try MCP-enhanced generation (no fallback recursion)
*/
private tryMCPGeneration;
private prepareMCPGenerationContext;
private logMCPConversationSummary;
/**
* The replay mode the budget check should size stored tool rows by: the
* request's `replayToolSteps`, else the instance's, else the builder's
* `"marker"` default — the same resolution BaseProvider applies before
* building messages, so the estimate matches the prompt.
*
* Reach: every `checkContextBudget` call on the generate and stream paths,
* including the post-compaction and post-truncation re-checks. It does NOT
* reach the compactor's own stage gates or the summarizer's sizing
* (`ContextCompactor`, `SummarizationEngine`): those still count tool rows
* at stored size, so under `"marker"` / `"off"` a stage may prune or
* summarize more history than the prompt would have carried. Threading the
* mode into the compactor is a follow-up, tracked in
* docs/features/context-compaction.md ("Context budget").
*/
private resolveToolReplayModeForBudget;
private ensureMCPGenerationBudget;
private compactMCPConversationForBudget;
private generateWithMCPProvider;
/**
* Direct provider generation (no MCP, no recursion)
* An explicit provider precedes orchestration context; otherwise a valid
* orchestration preference is tried before the existing provider fallback list.
*/
private directProviderGeneration;
/**
* Create tool-aware system prompt that informs AI about available tools
*/
/**
* Apply per-call tool filtering (whitelist/blacklist) to a ToolInfo array.
* Used to filter the tool list before building the system prompt.
*
* Resolves the SAME policy as the native gate (`BaseProvider`
* `applyToolFiltering`) — per-call options plus the instance-level `tools`
* config — so what the model is told about tools never diverges from the
* tools it can actually call.
*/
private applyToolInfoFiltering;
private createToolAwareSystemPrompt;
/**
* Execute tools if available through centralized registry
* Simplified approach without domain detection - relies on tool registry
*/
private detectAndExecuteTools;
/**
* BACKWARD COMPATIBILITY: Legacy streamText method
* Internally calls stream() and converts result format
*/
streamText(prompt: string, options?: Partial<StreamOptions>): Promise<AsyncIterable<string>>;
/**
* Stream AI-generated content in real-time using the best available provider.
* This method provides real-time streaming of AI responses with full MCP tool integration.
*
* @param options - Stream configuration options
* @param options.input - Input configuration object
* @param options.input.text - The text prompt to send to the AI (required)
* @param options.provider - AI provider to use ('auto', 'openai', 'anthropic', etc.)
* @param options.model - Specific model to use (e.g., 'gpt-4', 'claude-3-opus')
* @param options.temperature - Randomness in response (0.0 = deterministic, 2.0 = very random)
* @param options.maxTokens - Maximum tokens in response
* @param options.systemPrompt - System message to set AI behavior
* @param options.disableTools - Whether to disable MCP tool usage
* @param options.enableAnalytics - Whether to include usage analytics
* @param options.enableEvaluation - Whether to include response quality evaluation
* @param options.context - Additional context for the request
* @param options.evaluationDomain - Domain for specialized evaluation
* @param options.useKnowledgeGrounding - Whether to use the instance's configured knowledge for this call
*
* @returns Promise resolving to StreamResult with an async iterable stream
*
* @example
* ```typescript
* // Basic streaming usage
* const result = await neurolink.stream({
* input: { text: "Tell me a story about space exploration" }
* });
*
* // Consume the stream
* for await (const chunk of result.stream) {
* if ("content" in chunk) {
* process.stdout.write(chunk.content);
* }
* }
*
* // Advanced streaming with options
* const result = await neurolink.stream({
* input: { text: "Explain machine learning" },
* provider: "openai",
* model: "gpt-4",
* temperature: 0.7,
* enableAnalytics: true,
* context: { domain: "education", audience: "beginners" }
* });
*
* // Access metadata and analytics
* console.log(result.provider);
* console.log(result.analytics?.usage);
* ```
*
* @throws {Error} When input text is missing or invalid
* @throws {Error} When all providers fail to generate content
* @throws {Error} When conversation memory operations fail (if enabled)
*/
stream(options: StreamOptions | DynamicOptions): Promise<StreamResult>;
/** Re-enter request ownership when the caller drives a deferred async iterator. */
private bindConversationEventStream;
/**
* Body of {@link stream}, always entered through the instance log scope.
*
* Note the scope covers setting the stream up, not draining it: logs
* emitted while the consumer iterates the returned stream run in the
* consumer's async context, so they are attributed to whatever scope the
* consumer is in. Provider loops that run to completion inside this call
* are covered.
*/
private streamInInstanceScope;
private runStreamTurn;
/**
* Curator P2-3 / Reviewer Finding #2: stream-fallback that also covers
* errors thrown during async iteration (e.g. LiteLLM throwing inside
* `createLiteLLMTransformedStream`). The standard
* `runWithFallbackOrchestration` only catches errors thrown while the
* `StreamResult` is being created — once we hand the iterator back to
* the caller, errors raised during consumption used to bypass
* `providerFallback` / `modelChain`.
*
* This wrapper runs the orchestration to get an initial StreamResult,
* then wraps `result.stream` so that:
* - chunks are forwarded transparently while consumption succeeds
* - if iteration throws a model-access-denied error AND no chunks
* have been yielded yet, we resolve the next fallback target,
* emit `model.fallback`, and recurse
* - if chunks were already yielded, the error propagates (mid-stream
* recovery isn't safe — the consumer has half a response)
*/
private streamWithIterationFallback;
private executeStreamRequest;
/**
* Runs an internal call that re-enters the public generate() (the tool-routing
* router, the classifier router) without ending the outer turn's tool-cache
* state. generate()'s own `finally` resets these fields, so the outer turn
* would otherwise lose its repeat-call cache bypass for every later tool
* call. Restored by reference: the nested call assigns a new Set rather than
* mutating the outer one.
*
* Covers one turn re-entering itself. Two concurrent turns on one instance
* still share these fields.
*/
private preservingTurnState;
/**
* Pre-call tool routing for both stream() and generate() turns: runs the
* router LLM once per turn and appends the unpicked servers' registered tool
* names to `options.excludeTools` — the per-call denylist enforced by
* `baseProvider.applyToolFiltering`. No-op unless `toolRouting.enabled` is
* true and a non-empty server catalog has been supplied. Never throws (the
* resolver fails open to an empty exclusion list). Accepts both StreamOptions
* and GenerateOptions since both share the required fields.
*/
private applyToolRoutingExclusions;
/**
* Loads a bounded window of prior conversation turns for the router so a
* follow-up turn carries the context it needs to classify intent. Reads this
* turn's conversation memory (keyed by `context.sessionId`) with
* summarization disabled to keep the router cheap. Fails open to an empty
* list — routing then falls back to the current query alone (prior
* behaviour). On the first turn of a conversation memory may not be
* initialised yet; that also yields an empty list, which is fine since the
* opening message already carries its own context.
*/
private fetchRecentRoutingHistory;
/**
* Supplies (or replaces) the pre-call tool routing server catalog.
*
* For hosts that only know their tool servers after constructing NeuroLink
* (e.g. tools are registered per session/conversation). Routing must still
* be enabled via the constructor's `toolRouting.enabled` — setting servers
* alone does not activate it.
*/
setToolRoutingServers(servers: ToolRoutingServerDescriptor[]): void;
/** Knowledge-grounding engine health (null when it was not configured). */
getKnowledgeStatus(): KnowledgeEngineStatus | null;
private appendKnowledgeGroundingBlockToSystemPrompt;
/**
* Retrieve knowledge grounding for a public generate/stream call without
* mutating its options. The outer call boundary decides how to apply the
* returned context. Returns undefined when the call does not opt in,
* grounding is disabled/not applicable, or retrieval fails open.
*/
private retrieveKnowledgeGrounding;
private validateStreamRequestOptions;
private maybeHandleWorkflowStreamRequest;
private runStandardStreamRequest;
private drainStandardStreamOnce;
private drainStandardStream;
private settleStandardStreamDrain;
/** Wrap one provider stream with incremental TTS synthesis. */
private createIncrementalTTSStream;
/** Prevent provider fallback streams from duplicating outer streaming TTS. */
private deferProviderStreamTTS;
/**
* Prepare stream options: initialize memory, MCP, retrieval, orchestration,
* Ollama tool auto-disable, factory processing, and tool detection.
*/
private prepareStreamOptions;
/**
* Auto-disable tools for Ollama models that don't support them (stream mode).
* Prevents overwhelming smaller models with massive tool descriptions in the system message.
*/
private autoDisableOllamaStreamTools;
/**
* Set up event listeners for stream event capture (tool calls, HITL, UI components).
* Returns the shared event sequence array and a cleanup function to remove all listeners.
*/
private setupStreamEventListeners;
/**
* Handle fallback when the primary stream returns 0 chunks.
* Yields chunks from a fallback provider and updates metadata accordingly.
*/
private handleStreamFallback;
/**
* Store conversation memory after stream consumption is complete (called from finally block).
* Handles conversation memory storage in the background.
*/
private storeStreamConversationMemory;
/**
* Validate stream input with comprehensive error reporting
*/
private validateStreamInput;
/**
* Emit stream start events
*/
private emitStreamStartEvents;
/**
* Create the provider-backed MCP stream with the existing tool lifecycle.
* An explicit provider precedes a valid orchestration context preference;
* absent either, getBestProvider retains its normal automatic selection.
*/
private createMCPStream;
/**
* Process stream result
*/
private processStreamResult;
/**
* Emit stream end events
*/
private emitStreamEndEvents;
/**
* Create stream response
*/
private createStreamResponse;
/**
* Handle stream error with fallback
*/
private handleStreamError;
/**
* Get the EventEmitter instance to listen to NeuroLink events for real-time monitoring and debugging.
* This method provides access to the internal event system that emits events during AI generation,
* tool execution, streaming, and other operations for comprehensive observability.
*
* @returns EventEmitter instance that emits various NeuroLink operation events
*
* @example
* ```typescript
* // Basic event listening setup
* const neurolink = new NeuroLink();
* const emitter = neurolink.getEventEmitter();
*
* // Listen to generation events
* emitter.on('generation:start', (event) => {
* console.log(`Generation started with provider: ${event.provider}`);
* console.log(`Started at: ${new Date(event.timestamp)}`);
* });
*
* emitter.on('generation:end', (event) => {
* console.log(`Generation completed in ${event.responseTime}ms`);
* console.log(`Tools used: ${event.toolsUsed?.length || 0}`);
* });
*
* // Listen to streaming events
* emitter.on('stream:start', (event) => {
* console.log(`Streaming started with provider: ${event.provider}`);
* });
*
* emitter.on('stream:end', (event) => {
* console.log(`Streaming completed in ${event.responseTime}ms`);
* if (event.fallback) console.log('Used fallback streaming');
* });
*
* // Listen to tool execution events
* emitter.on('tool:start', (event) => {
* console.log(`Tool execution started: ${event.toolName}`);
* });
*
* emitter.on('tool:end', (event) => {
* console.log(`Tool ${event.toolName} ${event.success ? 'succeeded' : 'failed'}`);
* console.log(`Execution time: ${event.responseTime}ms`);
* });
*
* // Listen to tool registration events
* emitter.on('tools-register:start', (event) => {
* console.log(`Registering tool: ${event.toolName}`);
* });
*
* emitter.on('tools-register:end', (event) => {
* console.log(`Tool registration ${event.success ? 'succeeded' : 'failed'}: ${event.toolName}`);
* });
*
* // Listen to external MCP server events
* emitter.on('externalMCP:serverConnected', (event) => {
* console.log(`External MCP server connected: ${event.serverId}`);
* console.log(`Tools available: ${event.toolCount || 0}`);
* });
*
* emitter.on('externalMCP:serverDisconnected', (event) => {
* console.log(`External MCP server disconnected: ${event.serverId}`);
* console.log(`Reason: ${event.reason || 'Unknown'}`);
* });
*
* emitter.on('externalMCP:toolDiscovered', (event) => {
* console.log(`New tool discovered: ${event.toolName} from ${event.serverId}`);
* });
*
* // Advanced usage with error handling
* emitter.on('error', (error) => {
* console.error('NeuroLink error:', error);
* });
*
* // Clean up event listeners when done
* function cleanup() {
* emitter.removeAllListeners();
* }
*
* process.on('SIGINT', cleanup);
* process.on('SIGTERM', cleanup);
* ```
*
* @example
* ```typescript
* // Advanced monitoring with metrics collection
* const neurolink = new NeuroLink();
* const emitter = neurolink.getEventEmitter();
* const metrics = {
* generations: 0,
* totalResponseTime: 0,
* toolExecutions: 0,
* failures: 0
* };
*
* // Collect performance metrics
* emitter.on('generation:end', (event) => {
* metrics.generations++;
* metrics.totalResponseTime += event.responseTime;
* metrics.toolExecutions += event.toolsUsed?.length || 0;
* });
*
* emitter.on('tool:end', (event) => {
* if (!event.success) {
* metrics.failures++;
* }
* });
*
* // Log metrics every 10 seconds
* setInterval(() => {
* const avgResponseTime = metrics.generations > 0
* ? metrics.totalResponseTime / metrics.generations
* : 0;
*
* console.log('NeuroLink Metrics:', {
* totalGenerations: metrics.generations,
* averageResponseTime: `${avgResponseTime.toFixed(2)}ms`,
* totalToolExecutions: metrics.toolExecutions,
* failureRate: `${((metrics.failures / (metrics.toolExecutions || 1)) * 100).toFixed(2)}%`
* });
* }, 10000);
* ```
*
* **Available Events:**
*
* **Generation Events:**
* - `generation:start` - Fired when text generation begins
* - `{ provider: string, timestamp: number }`
* - `generation:end` - Fired when text generation completes (or fails / is aborted)
* - `{ provider: string, responseTime: number, toolsUsed?: string[], timestamp: number, success?: boolean, aborted?: boolean, error?: string }`
* - `success` is `false` for both failures and client aborts; `aborted: true`
* distinguishes the latter so consumers can route cancellations
* differently from real errors. Pipeline B's metrics span maps
* `aborted: true` events to `SpanStatus.WARNING` (not ERROR).
*
* **Streaming Events:**
* - `stream:start` - Fired when streaming begins
* - `{ provider: string, timestamp: number }`
* - `stream:end` - Fired when streaming completes
* - `{ provider: string, responseTime: number, fallback?: boolean }`
*
* **Tool Events:**
* - `tool:start` - Fired when tool execution begins
* - `{ toolName: string, timestamp: number }`
* - `tool:end` - Fired when tool execution completes
* - `{ toolName: string, responseTime: number, success: boolean, timestamp: number }`
* - `tools-register:start` - Fired when tool registration begins
* - `{ toolName: string, timestamp: number }`
* - `tools-register:end` - Fired when tool registration completes
* - `{ toolName: string, success: boolean, timestamp: number }`
*
* **External MCP Events:**
* - `externalMCP:serverConnected` - Fired when external MCP server connects
* - `{ serverId: string, toolCount?: number, timestamp: number }`
* - `externalMCP:serverDisconnected` - Fired when external MCP server disconnects
* - `{ serverId: string, reason?: string, timestamp: number }`
* - `externalMCP:serverFailed` - Fired when external MCP server fails
* - `{ serverId: string, error: string, timestamp: number }`
* - `externalMCP:toolDiscovered` - Fired when external MCP tool is discovered
* - `{ toolName: string, serverId: string, timestamp: number }`
* - `externalMCP:toolRemoved` - Fired when external MCP tool is removed
* - `{ toolName: string, serverId: string, timestamp: number }`
* - `externalMCP:serverAdded` - Fired when external MCP server is added
* - `{ serverId: string, config: MCPServerInfo, toolCount: number, timestamp: number }`
* - `externalMCP:serverRemoved` - Fired when external MCP server is removed
* - `{ serverId: string, timestamp: number }`
*
* **Error Events:**
* - `error` - Fired when an error occurs
* - `{ error: Error, context?: object }`
*
* @throws {Error} This method does not throw errors as it returns the internal EventEmitter
*
* @since 1.0.0
* @see {@link https://nodejs.org/api/events.html} Node.js EventEmitter documentation
* @see {@link NeuroLink.generate} for events related to text generation
* @see {@link NeuroLink.stream} for events related to streaming
* @see {@link NeuroLink.executeTool} for events related to tool execution
*/
getEventEmitter(): TypedEventEmitter<NeuroLinkEvents>;
/**
* Whether a HITL confirmation is still awaiting a response on THIS instance.
*
* Emitting `hitl:confirmation-response` is not proof the decision landed. The
* forwarding listener for that event is installed once at construction, so
* `emitter.emit(...)` reports a listener was invoked even when nothing is
* waiting — the pending set lives one hop further in, on the HITL manager, and
* holds the `resolve`/`reject` of the suspended tool call. An instance built
* after the confirmation was issued (a session rebuilt from persisted state)
* therefore accepts the event and resolves nothing.
*
* Returns `false` in two different situations, which it deliberately does not
* distinguish: HITL was never configured on this instance, and the id is
* unknown or already settled. Both mean "emitting a response here achieves
* nothing", which is the question this answers. A caller that needs to tell a
* configuration mistake from an expired confirmation should check the HITL
* config separately rather than read that into this boolean.
*
* This is advisory, not atomic: it reports the state at the moment it is
* called. Nothing stops the confirmation timing out immediately afterwards, so
* emit on the answer without an `await` in between. Over the case it exists
* for — an instance rebuilt from persisted state, whose pending set is empty
* and can never repopulate for an id it never issued — absence cannot become
* presence, so the answer cannot go stale in the unsafe direction.
*
* @param confirmationId - The id from the `hitl:confirmation-request` event
* @returns `true` only if this instance is still holding that confirmation
*
* @example
* ```typescript
* if (!neurolink.hasPendingHITLConfirmation(confirmationId)) {
* return refuse("This conversation has expired, so the action was not carried out.");
* }
* neurolink.getEventEmitter().emit("hitl:confirmation-response", { ... });
* ```
*/
hasPendingHITLConfirmation(confirmationId: string): boolean;
/**
* Returns the instance-level tool-dedup configuration, or `undefined` when
* toolDedup was not provided at construction time.
*
* The stored object is returned as-is whenever `toolDedup` was supplied,
* including when `enabled: false` — only the complete absence of a `toolDedup`
* option results in `undefined`.
*
* Called by `BaseProvider.applyToolFiltering` so the dedup pass uses the
* same config for every generate/stream call without threading an extra
* parameter through the full call stack.
*/
getToolDedupConfig(): ToolDedupConfig | undefined;
/**
* Returns the instance-level `tools` config (master switch, include/exclude
* lists, discovery mode), or `undefined` when not provided at construction.
*
* Called by `BaseProvider.applyToolFiltering` so the tool gate composes the
* instance policy with per-call options on every generate/stream call.
*/
/**
* The policy an entry point bound to `options`, or — for an internal path
* that built its own options — the instance policy narrowed by any
* per-call `toolRoots`. Never trusts a public field.
*/
private fileToolRootsFor;
/**
* A worker's tool config: it inherits this instance's file-tool roots, and a
* worker-level `fileRoots` may only narrow them (throws otherwise).
*/
private workerToolsConfig;
/** File-tool roots for one call: per-call roots narrow the instance's `tools.fileRoots`. */
private resolveFileToolRoots;
getToolsConfig(): ToolConfig | undefined;
/**
* Tools discovered via `search_tools` for a session (`tools.discovery`
* mode). Pinned tools are sent in full on every subsequent call of that
* session instead of being deferred — discovery cost is paid once.
* Reading refreshes the session's recency (LRU), so active conversations
* are never the ones evicted at the session cap.
*/
getDiscoveryPins(sessionKey: string): ReadonlySet<string>;
/**
* Pin discovered tools to a session (called by the `search_tools`
* meta-tool on hydration). Append-only within a session; the map is
* bounded by evicting the least-recently-used session past 1000 sessions.
*/
pinDiscoveredTools(sessionKey: string, toolNames: string[]): void;
/**
* Curator P1-1: synchronous credential health check for a single provider.
*
* Drives a tiny real call against the provider (1-token completion or
* `/models` listing depending on provider) to confirm the configured
* credentials are valid. Useful at startup so a service can refuse to
* boot if its primary provider's credentials are broken instead of
* discovering the problem on first user request.
*
* @example
* ```ts
* const health = await neurolink.checkCredentials({ provider: "litellm" });
* if (health.status !== "ok") {
* throw new Error(`provider not ready: ${health.detail}`);
* }
* ```
*
* @param input - the provider to check
* @returns `{ provider, status, detail }`. Possible status values:
* - `"ok"` — credentials valid and provider reachable
* - `"missing"` — required env / credentials not configured
* - `"expired"` — credentials present but rejected (401/403)
* - `"denied"` — credentials valid but team not whitelisted for any model
* - `"network"` — provider unreachable (timeout, ECONNREFUSED, DNS)
* - `"unknown"` — other error; consult `detail`
*/
checkCredentials(input: {
provider: string;
model?: string;
}): Promise<{
provider: string;
status: "ok" | "missing" | "expired" | "denied" | "network" | "unknown";
detail: string;
}>;
/**
* Emit tool start event with execution tracking
* @param toolName - Name of the tool being executed
* @param input - Input parameters for the tool
* @param startTime - Timestamp when execution started
* @returns executionId for tracking this specific execution
*/
emitToolStart(toolName: string, input: unknown, startTime?: number): string;
/**
* Emit tool end event with execution summary
* @param toolName - Name of the tool that finished
* @param result - Result from the tool execution
* @param error - Error message if execution failed
* @param startTime - When execution started
* @param endTime - When execution finished
* @param executionId - Optional execution ID for tracking
*/
emitToolEnd(toolName: string, result?: unknown, error?: string, startTime?: number, endTime?: number, executionId?: string): void;
/**
* Get current tool execution contexts for stream metadata
*/
getCurrentToolExecutions(): ToolExecutionContext[];
/**
* Get tool execution history
*/
getToolExecutionHistory(): ToolExecutionSummary[];
/**
* Clear current stream tool executions (called at stream start)
*/
clearCurrentStreamExecutions(): void;
/**
* Register a custom tool that will be available to all AI providers
* @param name - Unique name for the tool
* @param tool - Tool in MCPExecutableTool format (unified MCP protocol type)
*/
registerTool(name: string, tool: MCPExecutableTool, options?: ToolRegistrationOptions): void;
/**
* Set the context that will be passed to tools during execution
* This context will be merged with any runtime context passed by the AI model
* @param context - Context object containing session info, tokens, shop data, etc.
*/
setToolContext(context: Record<string, unknown>): void;
/**
* Get the current tool execution context
* @returns Current context or undefined if not set
*/
getToolContext(): Record<string, unknown> | undefined;
/**
* Clear the tool execution context
*/
clearToolContext(): void;
/**
* Register multiple tools at once - Supports both object and array formats
* @param tools - Object mapping tool names to MCPExecutableTool format OR Array of tools with names
*
* Object format (existing): { toolName: MCPExecutableTool, ... }
* Array format (Lighthouse compatible): [{ name: string, tool: MCPExecutableTool }, ...]
*/
registerTools(tools: Record<string, MCPExecutableTool> | Array<{
name: string;
tool: MCPExecutableTool;
}>): void;
/**
* Unregister a custom tool
* @param name - Name of the tool to remove
* @returns true if the tool was removed, false if it didn't exist
*/
unregisterTool(name: string): boolean;
/**
* Register a global tool middleware that runs on every tool execution.
* Middleware receives the tool, params, context, and a next() function.
* @param middleware - The middleware function to register
* @returns this (for chaining)
*/
useToolMiddleware(middleware: import("./types/index.js").ToolMiddleware): this;
/**
* Get all registered tool middlewares
*/
getToolMiddlewares(): import("./types/index.js").ToolMiddleware[];
/**
* Flush any pending batched tool calls immediately
*/
flushToolBatch(): Promise<void>;
/**
* Get the current MCP enhancements configuration
*/
getMCPEnhancementsConfig(): MCPEnhancementsConfig | undefined;
/**
* Update agentic loop report metadata for a conversation session.
* Upserts a report entry by reportId — updates existing or adds new.
* Only supported when using Redis conversation memory.
*
* @param sessionId The session identifier
* @param report The agentic loop report metadata to upsert
* @param userId Optional user identifier
* @throws Error if conversation memory is not initialized or is not Redis-backed
*
* @example
* ```typescript
* await neurolink.updateAgenticLoopReport("session-123", {
* reportId: "report-abc",
* reportType: "META",
* reportStatus: "INPROGRESS",
* });
* ```
*/
updateAgenticLoopReport(sessionId: string, report: import("./types/index.js").AgenticLoopReportMetadata, userId?: string): Promise<void>;
/**
* Get all registered custom tools
* @returns Map of tool names to MCPExecutableTool format
*/
getCustomTools(): Map<string, MCPExecutableTool>;
/**
* Add an in-memory MCP server (from git diff)
* Allows registration of pre-instantiated server objects
* @param serverId - Unique identifier for the server
* @param serverInfo - Server configuration
*/
addInMemoryMCPServer(serverId: string, serverInfo: MCPServerInfo): Promise<void>;
/**
* Get all registered in-memory servers as a Map for ID-based lookup.
*
* This method is primarily used when you need O(1) lookup by server ID,
* such as in `testMCPServer()` for checking if a specific server exists.
*
* @returns Map of server IDs to MCPServerInfo
* @see {@link getInMemoryServerInfos} for array-based access (useful for iteration/spreading)
*/
getInMemoryServers(): Map<string, MCPServerInfo>;
/**
* Get in-memory servers as an array of MCPServerInfo.
*
* This method is the canonical source for in-memory server filtering.
* It fetches from the centralized tool registry and filters servers
* with the "in-memory" category.
*
* Use this method when you need to:
* - Iterate over all in-memory servers
* - Spread servers into another array (e.g., in `listMCPServers()`)
* - Get a count of in-memory servers
*
* @returns Array of MCPServerInfo for in-memory servers
* @see {@link getInMemoryServers} for Map-based access (useful for ID lookups)
*/
getInMemoryServerInfos(): MCPServerInfo[];
/**
* Get auto-discovered servers as MCPServerInfo - ZERO conversion needed
* @returns Array of MCPServerInfo
*/
getAutoDiscoveredServerInfos(): MCPServerInfo[];
/**
* Execute a specific tool by name with robust error handling
* Supports both custom tools and MCP server tools with timeout, retry, and circuit breaker patterns
* @param toolName - Name of the tool to execute
* @param params - Parameters to pass to the tool
* @param options - Execution options including optional authentication context
* @returns Tool execution result
*/
executeTool<T = unknown>(toolName: string, params?: unknown, options?: {
/** Bound on ONE attempt. */
timeout?: number;
maxRetries?: number;
/**
* Bound on the WHOLE execution — every attempt plus the delays between
* them. Defaults to `timeout * (maxRetries + 1)`, which is what the
* retry loop already spent, so omitting it changes nothing.
*/
totalTimeoutMs?: number;
retryDelayMs?: number;
/** Disable tool result caching for this call */
disableToolCache?: boolean;
/** Bypass the request batcher for this call */
bypassBatcher?: boolean;
authContext?: {
userId?: string;
sessionId?: string;
user?: Record<string, unknown>;
[key: string]: unknown;
};
}): Promise<T>;
private createToolExecutionContext;
private executeToolWithSpan;
private prepareToolExecutionState;
private runPreparedToolExecution;
private handleSuccessfulToolExecution;
private handleFailedToolExecution;
/**
* Internal tool execution method with MCP enhancements wired in:
* - ToolCache: check/store cached results for non-destructive tools
* - ToolRouter: route to best server when same tool exists on multiple servers
* - Annotations: skip cache for destructive tools, retry safe tools on failure
* - Middleware: apply global middleware chain before execution
*/
private toolCacheRepeatKey;
private executeToolInternal;
/**
* Get tool annotations for execution decisions (cache, retry).
* Checks cached tool list first, falls back to inference from tool name.
*/
private getToolAnnotationsForExecution;
/**
* Get all available tools including custom and in-memory ones
* @returns Array of available tools with metadata
*/
private invalidateToolCache;
getAllAvailableTools(): Promise<ToolInfo[]>;
/**
* Get comprehensive status of all AI providers
* Primary method for provider health checking and diagnostics
*/
getProviderStatus(options?: {
quiet?: boolean;
}): Promise<ProviderStatus[]>;
/**
* Test a specific AI provider's connectivity and authentication
* @param providerName - Name of the provider to test
* @returns Promise resolving to true if provider is working
*/
testProvider(providerName: string): Promise<boolean>;
/**
* Internal method to test provider connection with minimal generation call.
* `signal` lets a caller (getProviderStatus's per-provider race) cancel the
* underlying request once it has already lost the race against its
* timeout, instead of leaving it running unobserved.
*/
private testProviderConnection;
/**
* Get the best available AI provider based on configuration and availability
* @param requestedProvider - Optional preferred provider name
* @returns Promise resolving to the best provider name
*/
getBestProvider(requestedProvider?: string): Promise<string>;
/**
* Get list of all available AI provider names
* @returns Array of supported provider names
*/
getAvailableProviders(): Promise<string[]>;
/**
* Validate if a provider name is supported
* @param providerName - Provider name to validate
* @returns True if provider name is valid
*/
isValidProvider(providerName: string): Promise<boolean>;
/**
* Get comprehensive MCP (Model Context Protocol) status information
* @returns Promise resolving to MCP status details
*/
getMCPStatus(): Promise<MCPStatus>;
/**
* List all configured MCP servers with their status
* @returns Promise resolving to array of MCP server information
*/
listMCPServers(): Promise<MCPServerInfo[]>;
/**
* Test connectivity to a specific MCP server
* @param serverId - ID of the MCP server to test
* @returns Promise resolving to true if server is reachable
*/
testMCPServer(serverId: string): Promise<boolean>;
/**
* Check if a provider has the required environment variables configured
* @param providerName - Name of the provider to check
* @returns Promise resolving to true if provider has required env vars
*/
hasProviderEnvVars(providerName: string): Promise<boolean>;
/**
* Perform comprehensive health check on a specific provider
* @param providerName - Name of the provider to check
* @param options - Health check options
* @returns Promise resolving to detailed health status
*/
checkProviderHealth(providerName: string, options?: {
timeout?: number;
includeConnectivityTest?: boolean;
includeModelValidation?: boolean;
cacheResults?: boolean;
}): Promise<{
provider: string;
isHealthy: boolean;
isConfigured: boolean;
hasApiKey: boolean;
lastChecked: Date;
error?: string;
warning?: string;
responseTime?: number;
configurationIssues: string[];
recommendations: string[];
}>;
/**
* Check health of all supported providers
* @param options - Health check options
* @returns Promise resolving to array of health statuses for all providers
*/
checkAllProvidersHealth(options?: {
timeout?: number;
includeConnectivityTest?: boolean;
includeModelValidation?: boolean;
cacheResults?: boolean;
}): Promise<Array<{
provider: string;
isHealthy: boolean;
isConfigured: boolean;
hasApiKey: boolean;
lastChecked: Date;
error?: string;
warning?: string;
responseTime?: number;
configurationIssues: string[];
recommendations: string[];
}>>;
/**
* Get a summary of provider health across all supported providers
* @returns Promise resolving to health summary statistics
*/
getProviderHealthSummary(): Promise<{
total: number;
healthy: number;
configured: number;
hasIssues: number;
healthyProviders: string[];
unhealthyProviders: string[];
recommendations: string[];
}>;
/**
* Clear provider health cache (useful for re-testing after configuration changes)
* @param providerName - Optional specific provider to clear cache for
*/
clearProviderHealthCache(providerName?: string): Promise<void>;
/**
* Get execution metrics for all tools
* @returns Object with execution metrics for each tool
*/
getToolExecutionMetrics(): Record<string, {
totalExecutions: number;
successfulExecutions: number;
failedExecutions: number;
successRate: number;
averageExecutionTime: number;
lastExecutionTime: number;
errorCategories: Record<string, number>;
}>;
/**
* NL-004: Set model alias/deprecation configuration.
* Models in the alias map will be warned, redirected, or blocked based on their action.
* @param config - Model alias configuration with aliases map
*/
setModelAliasConfig(config: import("./types/index.js").ModelAliasConfig): void;
/**
* Get circuit breaker status for all tools
* @returns Object with circuit breaker status for each tool
*/
getToolCircuitBreakerStatus(): Record<string, {
state: "closed" | "open" | "half-open";
failureCount: number;
isHealthy: boolean;
}>;
/**
* Reset circuit breaker for a specific tool
* @param toolName - Name of the tool to reset circuit breaker for
*/
resetToolCircuitBreaker(toolName: string): void;
/**
* Clear all tool execution metrics
*/
clearToolExecutionMetrics(): void;
/**
* Get comprehensive tool health report
* @returns Detailed health report for all tools
*/
getToolHealthReport(): Promise<{
totalTools: number;
healthyTools: number;
unhealthyTools: number;
tools: Record<string, {
name: string;
isHealthy: boolean;
metrics: {
totalExecutions: number;
successRate: number;
averageExecutionTime: number;
lastExecutionTime: number;
errorCategories: Record<string, number>;
};
circuitBreaker: {
state: "closed" | "open" | "half-open";
failureCount: number;
};
issues: string[];
recommendations: string[];
}>;
}>;
/**
* Initialize conversation memory if enabled (public method for explicit initialization)
* This is useful for testing or when you want to ensure conversation memory is ready
* @returns Promise resolving to true if initialization was successful, false otherwise
*/
ensureConversationMemoryInitialized(): Promise<boolean>;
/**
* Get conversation memory statistics (public API)
*/
getConversationStats(): Promise<import("./index.js").ConversationMemoryStats>;
/**
* Get complete conversation history for a specific session (public API)
* @param sessionId - The session ID to retrieve history for
* @returns Array of ChatMessage objects in chronological order, or empty array if session doesn't exist
*/
getConversationHistory(sessionId: string): Promise<ChatMessage[]>;
/**
* Clear conversation history for a specific session (public API)
*/
clearConversationSession(sessionId: string): Promise<boolean>;
/**
* Clear all conversation history (public API)
*/
clearAllConversations(): Promise<void>;
/**
* List all conversation sessions with metadata (public API)
* @param userId - Optional user ID to filter sessions (required for Redis storage)
* @returns Array of session list items with metadata
*/
listSessions(userId?: string): Promise<SessionListItem[]>;
/**
* Export a single session with full history and metadata (public API)
* @param sessionId - The session ID to export
* @param options - Export options
* @returns Session export object with full history
*/
exportSession(sessionId: string, options?: {
includeMetadata?: boolean;
format?: "json" | "csv";
}): Promise<SessionExport | null>;
/**
* Export all sessions for a user (public API)
* @param userId - Optional user ID (required for Redis storage)
* @param options - Export options
* @returns Array of session exports
*/
exportAllSessions(userId?: string, options?: {
includeMetadata?: boolean;
format?: "json" | "csv";
}): Promise<SessionExport[]>;
/**
* Store tool executions in conversation memory if enabled and Redis is configured
* @param sessionId - Session identifier
* @param userId - User identifier (optional)
* @param toolCalls - Array of tool calls
* @param toolResults - Array of tool results
* @param currentTime - Date when the tool execution occurred (optional)
* @returns Promise resolving when storage is complete
*/
storeToolExecutions(sessionId: string, userId: string | undefined, toolCalls: Array<{
toolCallId?: string;
toolName?: string;
args?: Record<string, unknown>;
[key: string]: unknown;
}>, toolResults: Array<{
toolCallId?: string;
result?: unknown;
error?: string;
[key: string]: unknown;
}>, currentTime?: Date): Promise<void>;
/**
* Check if tool execution storage is available.
*
* Now capability-based rather than Redis-specific: any configured memory
* backend implementing `storeToolExecution` qualifies. The old check
* required `STORAGE_TYPE === "redis"` AND a Redis manager by class name, so
* in-memory sessions reported false and silently skipped tool persistence.
*
* @returns whether the active memory backend can persist tool executions
*/
isToolExecutionStorageAvailable(): boolean;
/**
* Get the raw messages array for a session.
* Returns the full messages list without context filtering or summarization.
* @param sessionId - The session ID to retrieve messages for
* @returns Array of ChatMessage objects, or empty array if session doesn't exist
*/
getSessionMessages(sessionId: string, userId?: string): Promise<ChatMessage[]>;
/**
* Record one finished turn from a transport that does not go through
* generate()/stream() — a voice call — into the same conversation memory
* typed chat uses: same key, same manager, same summarization and title
* handling, same optional long-term memory write.
*
* Identity is never defaulted: `userId` is required, because typed chat falls
* back to a shared placeholder user when it is missing and a spoken turn must
* never be filed there. Recording the same `turnId` twice stores it once, so
* a caller can retry after an ambiguous failure.
*
* A turn with a blank user message, or a blank response and no tool events,
* is not stored (an empty assistant message makes some providers reject the
* next request); the result says which. Storage failures throw — the caller
* decides whether to retry or to carry on without the turn.
*
* @throws NeuroLinkError (INVALID_PARAMETERS) for a bad sessionId or userId
* @throws NeuroLinkError (MISSING_CONFIGURATION) when conversation memory is not enabled
* @throws ConversationMemoryError (STORAGE_ERROR) when the store rejects the write
*/
recordConversationTurn(options: RecordConversationTurnOptions): Promise<RecordConversationTurnResult>;
/**
* The most recent user/assistant messages of a conversation, bounded, for
* seeding a model that has no history of its own — a voice model at call
* start. Summaries, pinned skills, tool traffic and blank messages are left
* out; the oldest messages are dropped first when `maxChars` would be
* exceeded. Best effort: a missing conversation, disabled memory or a read
* failure yields an empty seed and never throws, because a call must start
* either way.
*/
getConversationSeed(options: ConversationSeedOptions): Promise<ConversationSeedMessage[]>;
/**
* Whether a conversation already holds messages under this user. A client
* may name the conversation a call appends to; the server accepts the name
* only when this is true for the user the session is verified as.
*/
conversationExists(options: {
sessionId: string;
userId: string;
}): Promise<boolean>;
/**
* Whether a stored conversation is owned by someone other than `userId`.
*
* The Redis manager includes the user in the key, so a different user simply
* never finds the conversation. The in-process manager keys sessions by id
* alone and only records the owner, so ownership is checked here: a session
* that exists with no recorded owner, or another one, is not `userId`'s.
*/
private isOwnedByAnotherUser;
/** Shared identity check for the voice-memory methods. */
private static assertConversationIdentity;
/**
* Replace the entire messages array for a session.
* @param sessionId - The session ID to update
* @param messages - The new messages array
* @param userId - Optional user ID for scoped Redis key lookup
*/
setSessionMessages(sessionId: string, messages: ChatMessage[], userId?: string): Promise<void>;
/**
* Modify the last assistant message in a session using a transformer function.
* Convenience wrapper around getSessionMessages/setSessionMessages.
* @param sessionId - The session ID to modify
* @param transformer - Function that receives the last assistant message content and returns the modified content
* @param userId - Optional user ID for scoped Redis key lookup
* @returns true if a message was modified, false if no assistant message was found
*/
modifyLastAssistantMessage(sessionId: string, transformer: (content: string) => string, userId?: string): Promise<boolean>;
/**
* Add an external MCP server
* Automatically discovers and registers tools from the server
* @param serverId - Unique identifier for the server
* @param config - External MCP server configuration
* @returns Operation result with server instance
*/
addExternalMCPServer(serverId: string, config: MCPServerInfo): Promise<ExternalMCPOperationResult<ExternalMCPServerInstance>>;
/**
* Remove an external MCP server
* Stops the server and removes all its tools
* @param serverId - ID of the server to remove
* @returns Operation result
*/
removeExternalMCPServer(serverId: string): Promise<ExternalMCPOperationResult<void>>;
/**
* List all external MCP servers
* @returns Array of server health information
*/
getExternalMCPServerHealth(): ExternalMCPServerHealth[];
listExternalMCPServers(): Array<{
serverId: string;
status: string;
toolCount: number;
uptime: number;
isHealthy: boolean;
config: MCPServerInfo;
}>;
/**
* Get external MCP server status
* @param serverId - ID of the server
* @returns Server instance or undefined if not found
*/
getExternalMCPServer(serverId: string): ExternalMCPServerInstance | undefined;
/**
* Execute a tool from an external MCP server
* @param serverId - ID of the server
* @param toolName - Name of the tool
* @param parameters - Tool parameters
* @param options - Execution options
* @returns Tool execution result
*/
executeExternalMCPTool(serverId: string, requestedToolName: string, parameters: JsonObject, options?: {
timeout?: number;
}): Promise<unknown>;
/**
* Resolve a tool name for a direct external MCP execution
* (`executeExternalMCPTool`) against the server's currently discovered
* tools.
*
* `experimental_repairToolCall` only runs inside the AI-SDK's own
* streamText/generateText loop (see toolCallRepair.ts), so a near-miss
* tool name reaching `executeExternalMCPTool` directly previously had no
* recovery at all — just the generic `Tool 'x' not found for server 'y'`
* `Error` that `ToolDiscoveryService.executeTool` throws deeper in the
* stack. This reuses the same name-matching policy
* (`resolveToolName`: case-insensitive exact → unambiguous substring →
* Levenshtein) so a repair here is accepted under exactly the rules
* already proven for the generation path.
*
* Exact match is a zero-risk fast path: it is returned unchanged before
* any resolution attempt, so every existing caller that already sends a
* valid name — including the AI-SDK path's `createExternalMCPTool`, which
* always executes with a name it just discovered — sees no behaviour
* change.
*
* Deliberately does NOT attempt a repair when the server is unknown or
* not connected: `ExternalServerManager.executeTool` throws distinct,
* more accurate errors for those states ("Server 'x' not found" /
* "not in connected state"), and resolving against an empty tool list
* here would replace those with a misleading "tool not found" instead.
*/
private resolveDirectMcpToolName;
/**
* Get all tools from external MCP servers
* @returns Array of external tool information
*/
getExternalMCPTools(): ExternalMCPToolInfo[];
/**
* Get tools from a specific external MCP server
* @param serverId - ID of the server
* @returns Array of tool information for the server
*/
getExternalMCPServerTools(serverId: string): ExternalMCPToolInfo[];
/**
* Test connection to an external MCP server
* @param config - Server configuration to test
* @returns Test result with connection status
*/
testExternalMCPConnection(config: MCPServerInfo): Promise<BatchOperationResult>;
/**
* Get external MCP server manager statistics
* @returns Statistics about external servers and tools
*/
getExternalMCPStatistics(): {
totalServers: number;
connectedServers: number;
failedServers: number;
totalTools: number;
totalConnections: number;
totalErrors: number;
};
/**
* Shutdown all external MCP servers
* Called automatically on process exit
*/
shutdownExternalMCPServers(): Promise<void>;
/**
* Get the global elicitation manager for interactive tool input
* Elicitation allows tools to request additional information from users during execution
* @returns The global ElicitationManager instance
* @example
* ```typescript
* const elicitationManager = neurolink.getElicitationManager();
*
* // Register a handler for confirmations
* elicitationManager.registerHandler(async (request) => {
* if (request.type === 'confirmation') {
* const answer = await askUser(request.message);
* return { confirmed: answer === 'yes' };
* }
* });
* ```
*/
getElicitationManager(): Promise<any>;
/**
* Register an elicitation handler for interactive tool input
* Handlers are called when tools need user input during execution
* @param handler - Function to handle elicitation requests
* @example
* ```typescript
* neurolink.registerElicitationHandler(async (request) => {
* switch (request.type) {
* case 'confirmation':
* return { confirmed: await confirmWithUser(request.message) };
* case 'text':
* return { value: await promptUser(request.message) };
* case 'select':
* return { value: await selectFromOptions(request.options) };
* }
* });
* ```
*/
registerElicitationHandler(handler: (request: unknown) => Promise<unknown>): Promise<void>;
/**
* Get the multi-server manager for load balancing and coordination
* Allows managing multiple MCP servers with failover and load balancing
* @returns The global MultiServerManager instance
* @example
* ```typescript
* const multiServer = neurolink.getMultiServerManager();
*
* // Create a server group with load balancing
* await multiServer.createServerGroup('ai-tools', {
* servers: ['openai-server', 'anthropic-server'],
* strategy: 'round-robin'
* });
* ```
*/
getMultiServerManager(): Promise<any>;
/**
* Get the enhanced tool discovery service
* Provides advanced search, filtering, and compatibility checking for tools
* @returns EnhancedToolDiscovery instance
* @example
* ```typescript
* const discovery = neurolink.getEnhancedToolDiscovery();
*
* // Search for tools by criteria
* const results = await discovery.searchTools({
* category: 'data-processing',
* capabilities: ['streaming', 'batch'],
* minReliability: 0.9
* });
* ```
*/
getEnhancedToolDiscovery(): Promise<any>;
/**
* Get the MCP registry client for discovering servers from registries
* Supports multiple registry sources (official, community, custom)
* @returns The global MCPRegistryClient instance
* @example
* ```typescript
* const registryClient = neurolink.getMCPRegistryClient();
*
* // Search for servers
* const servers = await registryClient.searchServers({
* query: 'database',
* categories: ['data', 'storage']
* });
*
* // Get a well-known server config
* const githubServer = registryClient.getWellKnownServer('github');
* ```
*/
getMCPRegistryClient(): Promise<any>;
/**
* Expose a NeuroLink agent as an MCP tool
* This allows agents to be called by other systems via MCP
* @param agent - The agent to expose (must include id, name, description, and execute)
* @param options - Exposure configuration options (prefix, defaultAnnotations, etc.)
* @returns The exposed tool definition
* @example
* ```typescript
* const agent = {
* id: 'my-agent',
* name: 'My Agent',
* description: 'An agent that processes data',
* execute: async (params) => { ... }
* };
* const tool = await neurolink.exposeAgentAsTool(agent, {
* prefix: 'agent_'
* });
* ```
*/
exposeAgentAsTool(agent: {
id: string;
name: string;
description: string;
execute: (params: unknown, context?: unknown) => Promise<unknown>;
}, options?: {
prefix?: string;
includeMetadataInDescription?: boolean;
wrapWithContext?: boolean;
executionTimeout?: number;
enableLogging?: boolean;
}): Promise<import("./index.js").ExposureResult>;
/**
* Expose a workflow as an MCP tool
* This allows workflows to be called by other systems via MCP
* @param workflow - The workflow to expose (must include id, name, description, and execute)
* @param options - Exposure configuration options (prefix, defaultAnnotations, etc.)
* @returns The exposed tool definition
* @example
* ```typescript
* const workflow = {
* id: 'data-pipeline',
* name: 'Data Pipeline',
* description: 'Runs the data processing pipeline',
* execute: async (params) => { ... }
* };
* const tool = await neurolink.exposeWorkflowAsTool(workflow, {
* prefix: 'workflow_'
* });
* ```
*/
exposeWorkflowAsTool(workflow: {
id: string;
name: string;
description: string;
execute: (params: unknown, context?: unknown) => Promise<unknown>;
steps?: Array<{
id: string;
name: string;
description?: string;
}>;
}, options?: {
prefix?: string;
includeMetadataInDescription?: boolean;
wrapWithContext?: boolean;
executionTimeout?: number;
enableLogging?: boolean;
}): Promise<import("./index.js").ExposureResult>;
/**
* Get the tool integration manager for middleware and elicitation
* Provides advanced tool wrapping with confirmation, timeout, retry, etc.
* @returns The global ToolIntegrationManager instance
* @example
* ```typescript
* const integration = neurolink.getToolIntegrationManager();
*
* // Register a tool with middleware
* integration.registerTool(myTool, {
* timeout: 30000,
* retries: 3,
* requireConfirmation: true
* });
* ```
*/
getToolIntegrationManager(): Promise<any>;
/**
* Convert NeuroLink tools to MCP format
* Useful for exposing local tools to external MCP clients
* @param tools - Array of NeuroLink tool definitions
* @param options - Conversion options
* @returns Array of MCP-formatted tools
* @example
* ```typescript
* const mcpTools = neurolink.convertToolsToMCPFormat([
* { name: 'myTool', description: 'Does something', execute: async () => {} }
* ]);
* ```
*/
convertToolsToMCPFormat(tools: Array<{
name: string;
description: string;
execute?: (params: unknown) => unknown;
}>, options?: {
namespacePrefix?: string;
}): Promise<any>;
/**
* Convert MCP tools to NeuroLink format
* Useful for importing tools from external MCP servers
* @param tools - Array of MCP tool definitions
* @param options - Conversion options
* @returns Array of NeuroLink-formatted tools
* @example
* ```typescript
* const neurolinkTools = neurolink.convertToolsFromMCPFormat(externalTools, {
* removeNamespacePrefix: 'external_'
* });
* ```
*/
convertToolsFromMCPFormat(tools: Array<{
name: string;
description: string;
inputSchema?: unknown;
}>, options?: {
removeNamespacePrefix?: string;
}): Promise<any>;
/**
* Get tool annotations and safety information
* Provides insights about tool behavior, safety levels, and retry-ability
* @param toolName - Name of the tool to analyze
* @returns Tool annotation summary
* @example
* ```typescript
* const annotations = await neurolink.getToolAnnotations('deleteFile');
* // Returns: { destructive: true, requiresConfirmation: true, safeToRetry: false }
* ```
*/
getToolAnnotations(toolName: string): Promise<{
annotations: MCPToolAnnotations;
summary: string;
} | null>;
/**
* Convert external MCP tools to Vercel AI SDK tool format
* This allows AI providers to use external tools directly
*/
private convertExternalMCPToolsToAISDKFormat;
/**
* Convert JSON Schema to AI SDK compatible format
* For now, we'll skip schema validation and let the AI SDK handle parameters dynamically
*/
private convertJSONSchemaToAISDKFormat;
/**
* Unregister external MCP tools from a specific server
*/
private unregisterExternalMCPToolsFromRegistry;
/**
* Unregister a specific external MCP tool from the main registry
*/
private unregisterExternalMCPToolFromRegistry;
/**
* Lazily initialize conversation memory when needed
* This is called the first time a generate or stream operation is performed
*/
private lazyInitializeConversationMemory;
/**
* Unregister all external MCP tools from the main registry
*/
private unregisterAllExternalMCPToolsFromRegistry;
/**
* Create an evaluation pipeline with the specified configuration or preset.
* Pipelines orchestrate multiple scorers to evaluate AI responses comprehensively.
*
* @param configOrPreset - Pipeline configuration object or preset name
* @returns Initialized evaluation pipeline
*
* @example Using a preset
* ```typescript
* const neurolink = new NeuroLink();
* const pipeline = await neurolink.createEvaluationPipeline('rag');
* const result = await pipeline.execute({
* query: 'What is the capital of France?',
* response: 'Paris is the capital of France.',
* context: ['France is a country in Europe. Paris is its capital.']
* });
* console.log(result.overallScore, result.passed);
* ```
*
* @example Using custom configuration
* ```typescript
* const pipeline = await neurolink.createEvaluationPipeline({
* name: 'custom-quality',
* scorers: [
* { id: 'toxicity', config: { threshold: 0.9 } },
* { id: 'hallucination', config: { weight: 1.5 } },
* { id: 'answer-relevancy' }
* ],
* aggregation: { method: 'weighted' },
* passThreshold: 0.8
* });
* ```
*/
createEvaluationPipeline(configOrPreset: import("./types/index.js").PipelineConfig | "safety" | "rag" | "quality" | "comprehensive" | "minimal" | "summarization" | "customerSupport" | "codeGeneration"): Promise<import("./evaluation/pipeline/evaluationPipeline.js").EvaluationPipeline>;
/**
* Run the `decide` inference type: evaluate a `state` against a batch of
* named, typed questions and get one typed answer each.
*
* This is NOT {@link NeuroLink.evaluate}, which scores an already-generated
* response with RAGAS scorers. `decide` calls a decision model — one that
* emits no text at all and returns calibrated, typed judgements your code
* can branch on.
*
* **Batch, never fan out.** On a decision model, latency is flat in
* question count (measured: 1 question 393ms, 400 questions 465ms) while
* concurrent requests queue. Put every question you might need into one
* call — speculative questions are nearly free, a second round trip is not.
*
* A `choice` answer carries the full probability distribution, so one
* question over N options also ranks all N.
*
* @example
* ```typescript
* const result = await neurolink.decide({
* state: ticketText,
* questions: {
* team: {
* type: "choice",
* instructions: "Which team should handle this?",
* criteria: { billing: "Payments", technical: "Bugs", sales: "Pricing" },
* },
* urgent: { type: "boolean", instructions: "Is this urgent?" },
* },
* });
* const team = readDecisionChoice(result.answers, "team");
* if (team && team.confidence > 0.7) { assign(team.choice); }
* ```
*
* @throws when no decision provider is configured, or the call fails
*/
decide(options: DecisionOptions): Promise<DecisionResult>;
/**
* The provider a decision call will go to, resolved exactly as `decide()`
* resolves it: an explicit name (or alias) wins, else the first configured
* decision provider counting SDK credentials as well as the environment.
*/
private resolveDecisionProviderName;
/**
* The model `decide()` sends when the caller names none: the one the
* provider factory registered — `<PROVIDER>_MODEL` as it read at
* registration, else the descriptor default. `decisionLimits()` must
* report this same value rather than re-read the environment, or a
* `LAYA_MODEL` changed after registration would size a state against one
* checkpoint's window while the pre-flight check applies another's.
* Before registration has run the registry is empty, and the factory will
* read the environment when it does — so that read is returned here.
*/
private registeredDecisionModel;
/**
* What the decision provider can read, resolved for one model — the same
* figures the pre-flight refusal compares against, so a host can size the
* questions it adds through `decisionHooks` (or a state it is about to
* send) before the call instead of after the refusal.
*
* Provider resolution is `decide()`'s; the model is `query.model`, else
* the model `decide()` would send (see `registeredDecisionModel`); the
* per-model entry is flattened over the base. `enforcedLocally` says
* whether NeuroLink refuses an over-limit request itself (Laya) or only
* reports the server's ceiling (TypeSafe, whose limits are advisory).
*
* Returns null when no decision provider is configured and none is named —
* parity with `tryDecide` — and for a provider that declares no limits.
* A named provider is read whether or not it is configured: its limits are
* facts about the model, not about this host's credentials.
*/
decisionLimits(query?: DecisionLimitsQuery): DecisionLimitsReading | null;
/**
* `decisionHooks.hookTimeoutMs`, validated once per instance. A timer
* honours delays from 1 to 2^31 − 1 ms and fires at once for anything
* else — so `Infinity`, meant to lift the bound, would instead time every
* hook out immediately, as would 0, NaN or a negative. Such a value falls
* back to the default with one warning.
*/
private decisionHookTimeout;
/**
* `EventEmitter.emit` runs listeners synchronously and rethrows whatever
* they throw. A decision event is observe-only, so a throwing listener
* must not be able to change the turn's routing or compaction outcome.
*/
private emitDecisionEvent;
/**
* The one path every fail-open decision takes — a host's own `tryDecide`
* and all five built-in sites (routing, tool routing, both compaction
* stages, RAG planning) end up here.
*
* For a request stamped with a {@link DecisionSite} this is also where the
* host's `decisionHooks` ride along:
*
* 1. `extendQuestions` may add questions; a throw is logged and ignored.
* 2. Each host question is validated (invalid ones dropped, with a warning)
* and namespaced `host__N` on the wire, so it can never collide with
* NeuroLink's own ids (`difficulty`, `server__N`, `msg__N`, …).
* 3. Additions are capped at the provider's `maxQuestions` minus
* NeuroLink's own count. Without the cap a host adding one question too
* many would have the WHOLE request refused, and NeuroLink's own routing
* would silently degrade to the heuristic — the host's problem would
* become NeuroLink's. A provider that declares no `maxQuestions`
* (TypeSafe, XOR) takes every well-formed addition.
* 4. Answers are split: the consumer sees only its own ids, exactly as it
* would without hooks; `onAnswers` gets the host's answers under the
* host's original ids plus the full result.
*
* The request's `images` and `video` ride along untouched — `merged` and
* the wire call spread the request, and only `state`, the questions and
* the result are cloned — so an XOR request keeps its Buffers and its
* result keeps `mediaBytes`.
*
* `decision:before` / `decision:after` fire once per site call on the
* instance emitter, observe-only, whether or not the call produced a
* result. Without hooks and listeners, the decision payload — state and
* questions — is exactly what the consumer built; the outer request's
* per-call `credentials` are still forwarded, so the account and base URL
* on the wire can differ from the instance's.
*
* Fail-open by construction, in three layers. Without a decision provider
* nothing runs at all — no hook, no event — and the consumer gets the null
* it always got. Everything a host can reach (its hook's return value, the
* event payloads, the answer split, `onAnswers`) runs inside one `try`;
* whatever a host throws, the consumer gets what `tryRunDecide` alone
* would have given it. And every host-facing value is a `structuredClone`,
* so a hook or listener that edits what it is handed edits neither the
* wire request nor the consumer's own question objects.
*
* `outer` is what the surrounding `generate()` / `stream()` supplies —
* per-call credentials, abort signal, ids — and wins over the same fields
* on the request.
*/
private siteDecide;
/**
* `decide()` proper. A request stamped with a site — every built-in
* consumer's, and a host-wired consumer's — adds `decision.site` and
* `decision.host_question_count` (the `host__N` ids the funnel namespaced)
* to both spans, so a trace can tell a routing decision from a compaction
* one and see how many questions the host rode along with.
*/
private runDecide;
/**
* Fail-open {@link NeuroLink.decide}: returns null on any failure instead
* of throwing.
*
* This is the contract every internal consumer relies on. A decision model
* that is unconfigured, slow, rate-limited or down must never change
* NeuroLink's observable behaviour — the caller falls back to whatever it
* did before the decision was available.
*
* It also takes more questions than a provider's per-request cap: the map is
* split at the cap, the batches run a few at a time, and the answers come
* back joined. A batch that fails costs only its own answers, which every
* consumer already reads as "no decision"; null comes back only when every
* batch failed. `decide()` itself stays strict and refuses an over-cap request.
*
* A request stamped with a `site` — every built-in consumer stamps its own,
* so a `RAGPipeline` or `ClassifierRouter` a host wired to this method gets
* the same treatment as NeuroLink's internal ones — also runs the host's
* `decisionHooks` and emits `decision:before` / `decision:after`.
*/
tryDecide(options: DecisionCallerOptions): Promise<DecisionResult | null>;
/**
* Transcribe a recording to text through any STT provider, with the
* optional layers that make self-hosted engines usable: a dictionary of
* names and jargon (sent to the engine as context, then repaired after the
* fact behind a `tryDecide` guard), an LLM rewrite, a second-opinion
* engine, and a fallback engine for audio the primary cannot read.
*
* The provider defaults to `NEUROLINK_STT_PROVIDER`, then the first
* configured STT provider, then the built-in local engine, so an instance
* with no STT configuration still transcribes. Per-call
* `credentials.stt` beats the instance's, which beats the environment.
*
* @example
* ```typescript
* const result = await neurolink.transcribe({
* audio: "./meeting.wav",
* dictionary: [{ term: "NeuroLink", heardAs: ["neural link"] }],
* });
* console.log(result.text, result.engine.provider, result.steps);
* ```
*
* @throws STTError when the audio cannot be read, the provider is unknown,
* or the engine fails with no fallback that covers errors
*/
transcribe(options: TranscribeOptions): Promise<TranscribeResult>;
/**
* Transcribe a live stream of PCM16LE frames. Engines with a native stream
* use it; every other engine is streamed through the chunked adapter
* (energy-gated utterances, rolling re-transcription, LocalAgreement
* commits). Emits `interim`, `final`, `corrected` and the other
* {@link TranscribeStreamEvent}s; the same correction, fallback and
* provider rules as {@link NeuroLink.transcribe} apply per utterance.
*
* @example
* ```typescript
* for await (const event of neurolink.transcribeStream({ audio: micFrames })) {
* if (event.type === "final") console.log(event.text);
* }
* ```
*/
transcribeStream(options: TranscribeStreamOptions): AsyncIterable<TranscribeStreamEvent>;
/**
* Instance `credentials.stt` with a per-call `stt` slice merged on top,
* provider by provider and field by field (a per-call `apiKey` keeps the
* instance's `baseURL`). Undefined when neither has one.
*/
private resolveSTTCredentials;
/**
* The STT provider for a call: an explicit name (or alias) as its
* canonical name, a name only a caller-registered handler knows as is, and
* no name as the default provider for these credentials.
*/
private resolveSTTProvider;
/**
* Everything the transcribe orchestration needs from this instance: STT
* handlers with the request's credentials applied, provider resolution,
* the fail-open decision model and a rewrite model.
*/
private buildTranscribeDeps;
/**
* The correction layer's two model calls: `decide` is {@link tryDecide}
* (fail-open — `null` on any failure), `rewrite` a plain text call with
* tools and memory off (`onPartial`, when given, receives the finished
* text once — see the note inside on why it is not streamed) and aborted
* at its timeout.
*/
private buildSTTCorrectionDeps;
/**
* `tryDecide` without the site handling: the try/catch around the call.
* Dispatches through `this.decide`, so a subclass override or an instance
* patch on the public method sees every decision — the funnel's routing,
* tool routing and compaction calls included, not only a host's own.
*/
private tryRunDecide;
/**
* One `decide()` call, or, when the question map is longer than the resolved
* provider's cap, several run a few at a time with their results joined. The
* provider is resolved here exactly as `decide()` resolves it, so the cap that
* is read is the cap of the provider that will answer.
*/
private decideInBatches;
/**
* Evaluate an AI response using the specified pipeline or scorers.
* This is a convenience method that creates a pipeline and executes it in one call.
*
* @param input - Scorer input containing query, response, and optional context
* @param options - Evaluation options including pipeline preset or custom scorers
* @returns Evaluation pipeline result with scores and pass/fail status
*
* @example Using a preset
* ```typescript
* const neurolink = new NeuroLink();
* const result = await neurolink.evaluate(
* {
* query: 'Explain quantum computing',
* response: 'Quantum computing uses qubits...'
* },
* { pipeline: 'quality' }
* );
* console.log(`Score: ${result.overallScore}, Passed: ${result.passed}`);
* ```
*
* @example Using specific scorers
* ```typescript
* const result = await neurolink.evaluate(
* {
* query: 'What causes rain?',
* response: 'Rain is caused by water vapor...',
* context: ['The water cycle involves evaporation...']
* },
* { scorers: ['hallucination', 'faithfulness', 'answer-relevancy'] }
* );
* ```
*
* @example Full RAG evaluation
* ```typescript
* const result = await neurolink.evaluate(
* {
* query: 'Who wrote Hamlet?',
* response: 'Shakespeare wrote Hamlet in 1600.',
* context: ['William Shakespeare wrote Hamlet around 1600-1601.'],
* groundTruth: 'William Shakespeare'
* },
* { pipeline: 'rag' }
* );
* ```
*/
evaluate(input: import("./types/index.js").ScorerInput, options?: {
/** Pipeline preset to use */
pipeline?: "safety" | "rag" | "quality" | "comprehensive" | "minimal" | "summarization" | "customerSupport" | "codeGeneration";
/** Specific scorers to use (alternative to pipeline) */
scorers?: string[];
/** Pass threshold override (0-1) */
passThreshold?: number;
/** Execution mode */
executionMode?: "parallel" | "sequential";
/** Correlation ID for tracing */
correlationId?: string;
/** Overall evaluation timeout in milliseconds */
timeoutMs?: number;
}): Promise<import("./types/index.js").PipelineResult>;
/**
* Score a response using a single scorer.
* Useful for quick, targeted evaluations without the overhead of a full pipeline.
*
* @param scorerId - The ID of the scorer to use (e.g., 'toxicity', 'hallucination')
* @param input - Scorer input containing query, response, and optional context
* @param config - Optional scorer configuration overrides
* @returns Score result with value, reasoning, and pass/fail status
*
* @example Basic scoring
* ```typescript
* const neurolink = new NeuroLink();
* const result = await neurolink.score('toxicity', {
* query: '',
* response: 'This is a helpful response about cooking recipes.'
* });
* console.log(`Toxicity Score: ${result.score}/10, Passed: ${result.passed}`);
* ```
*
* @example Hallucination detection
* ```typescript
* const result = await neurolink.score('hallucination', {
* query: 'What year was the Eiffel Tower built?',
* response: 'The Eiffel Tower was built in 1889.',
* context: ['The Eiffel Tower was constructed from 1887-1889.']
* });
* console.log(`Score: ${result.score}, Reasoning: ${result.reasoning}`);
* ```
*
* @example With custom threshold
* ```typescript
* const result = await neurolink.score(
* 'faithfulness',
* {
* query: 'Summarize the article',
* response: 'The article discusses...',
* context: ['Article content here...']
* },
* { threshold: 0.85, weight: 1.5 }
* );
* ```
*/
score(scorerId: string, input: import("./types/index.js").ScorerInput, config?: import("./types/index.js").ScorerConfig): Promise<import("./types/index.js").ScoreResult>;
/**
* Get a list of all available scorers and their metadata.
* Useful for discovering what evaluation capabilities are available.
*
* @param options - Filter options
* @returns Array of scorer metadata
*
* @example List all scorers
* ```typescript
* const neurolink = new NeuroLink();
* const scorers = await neurolink.getAvailableScorers();
* for (const scorer of scorers) {
* console.log(`${scorer.id}: ${scorer.description} (${scorer.type})`);
* }
* ```
*
* @example Filter by category
* ```typescript
* const safetyScorers = await neurolink.getAvailableScorers({
* category: 'safety'
* });
* console.log('Safety scorers:', safetyScorers.map(s => s.id));
* ```
*
* @example Filter by type
* ```typescript
* const ruleBasedScorers = await neurolink.getAvailableScorers({
* type: 'rule'
* });
* ```
*/
getAvailableScorers(options?: {
/** Filter by category */
category?: import("./types/index.js").ScorerCategory;
/** Filter by type */
type?: import("./types/index.js").ScorerType;
}): Promise<import("./types/index.js").ScorerMetadata[]>;
/**
* Get a list of available evaluation pipeline presets.
* Presets are pre-configured pipelines for common evaluation scenarios.
*
* @returns Array of preset names
*
* @example
* ```typescript
* const neurolink = new NeuroLink();
* const presets = await neurolink.getEvaluationPresets();
* console.log('Available presets:', presets);
* // Output: ['safety', 'rag', 'quality', 'comprehensive', 'minimal', ...]
* ```
*/
getEvaluationPresets(): Promise<string[]>;
/**
* Get details of a specific evaluation preset.
*
* @param presetName - Name of the preset
* @returns Pipeline configuration for the preset
*
* @example
* ```typescript
* const neurolink = new NeuroLink();
* const ragPreset = await neurolink.getEvaluationPreset('rag');
* console.log('RAG preset scorers:', ragPreset.scorers.map(s => s.id));
* console.log('Pass threshold:', ragPreset.passThreshold);
* ```
*/
getEvaluationPreset(presetName: "safety" | "rag" | "quality" | "comprehensive" | "minimal" | "summarization" | "customerSupport" | "codeGeneration"): Promise<import("./types/index.js").PipelineConfig>;
/**
* Create an Agent instance for multi-agent orchestration.
*
* Agents are specialized AI entities with defined instructions, tools, and behavior.
* They can be composed into networks for complex task orchestration.
*
* @param definition - Agent definition specifying behavior and capabilities
* @returns A new Agent instance
*
* @example
* ```typescript
* const researcher = neurolink.createAgent({
* id: 'researcher',
* name: 'Research Agent',
* description: 'Searches and analyzes information from various sources',
* instructions: 'You are a research assistant. Search thoroughly and cite sources.',
* tools: ['websearchGrounding', 'readFile'],
* model: 'gpt-4o'
* });
*
* const result = await researcher.execute('Find recent AI breakthroughs');
* ```
*
* @see {@link AgentDefinition} for definition options
* @see {@link Agent} for agent methods
* @since 8.38.0
*/
createAgent(definition: AgentDefinition): Promise<import("./agent/agent.js").Agent>;
/**
* Create an AgentNetwork for multi-agent orchestration.
*
* Networks coordinate multiple agents, workflows, and tools with intelligent
* LLM-powered routing. The router agent analyzes tasks and delegates to
* the most appropriate primitive.
*
* @param config - Network configuration with agents, workflows, and routing settings
* @returns A new AgentNetwork instance
*
* @example
* ```typescript
* const network = neurolink.createNetwork({
* name: 'Content Team',
* description: 'Collaborative content creation pipeline',
* agents: [
* {
* id: 'researcher',
* name: 'Researcher',
* description: 'Finds and verifies information',
* instructions: 'Research topics thoroughly...',
* },
* {
* id: 'writer',
* name: 'Writer',
* description: 'Creates engaging content',
* instructions: 'Write clear, engaging content...',
* },
* {
* id: 'editor',
* name: 'Editor',
* description: 'Reviews and improves content',
* instructions: 'Review for clarity and accuracy...',
* }
* ],
* router: {
* model: 'gpt-4o',
* confidenceThreshold: 0.7
* }
* });
*
* const result = await network.execute({
* message: 'Write an article about quantum computing'
* });
* ```
*
* @see {@link AgentNetworkConfig} for configuration options
* @see {@link AgentNetwork} for network methods
* @since 8.38.0
*/
createNetwork(config: AgentNetworkConfig): Promise<import("./agent/agentNetwork.js").AgentNetwork>;
/**
* Create a worker-mode NeuroLink instance for sub-agent execution.
*
* Worker mode is the framework-provided version of the config block every
* consumer used to copy by hand: conversation memory OFF, orchestration
* OFF, observability inherited from this instance (worker spans join the
* host's tracer provider), credentials inherited, the host's tool registry shared (so
* worker tool calls reuse the host's connections), and an internal log
* bridge attached with a caller-supplied tag.
*
* Dispose the worker (`worker.dispose()`) when done — `runIsolatedAgent`
* does this automatically in a `finally`.
*
* @param options - Worker options (log tag/sink, registry sharing, config)
* @returns A new worker-mode NeuroLink instance
* @see {@link WorkerInstanceOptions}
*/
createWorkerInstance(options?: WorkerInstanceOptions): NeuroLink;
/**
* Run an isolated sub-agent: a worker instance (see
* {@link createWorkerInstance}) executes a tool-using research pass under
* the turn budget (wrap-up nudge, stall watchdog, honest `stopReason`),
* then an extraction pass ALWAYS runs tools-off on its own timeout with a
* structured-recovery ladder and corrective re-asks. A non-empty execution
* record never produces an empty result (mechanical digest fallback), a
* parent `abortSignal` stops everything cleanly, and `options.leg` enables
* leashed mode with TTL'd resume handles ({@link continueAgent} /
* {@link stopAgent}).
*
* @param definition - Agent definition (+ optional structured extraction)
* @param input - Task input: string or structured object
* @param options - Run options (abort, overrides, tool context, events, leg)
* @returns The run outcome
* @see {@link IsolatedAgentDefinition}
* @see {@link AgentRunOptions}
* @see {@link AgentRunOutcome}
*/
runIsolatedAgent(definition: IsolatedAgentDefinition, input: string | Record<string, unknown>, options?: AgentRunOptions): Promise<AgentRunOutcome>;
/**
* Resume a leashed isolated-agent run by handle. `guidance`, when given,
* is appended as a user turn before the next leg — the supervisor's
* re-steering channel. An expired handle returns its tombstoned final
* outcome exactly once.
*
* @param handle - Handle from an `in_progress` {@link AgentRunOutcome}
* @param guidance - Optional supervisor guidance for the next leg
* @returns The next leg's outcome (or the final outcome)
*/
continueAgent(handle: string, guidance?: string): Promise<AgentRunOutcome>;
/**
* Stop a leashed isolated-agent run: dispose its worker and return the
* final outcome (mechanical digest over everything gathered so far).
*
* @param handle - Handle from an `in_progress` {@link AgentRunOutcome}
* @returns The final outcome
*/
stopAgent(handle: string): Promise<AgentRunOutcome>;
/**
* Register an isolated agent as a delegation tool on THIS instance, so
* its existing generate() loop can delegate — no second router generate.
* Framework policy (per-turn caps, depth withholding, a process-wide
* concurrency pool with queue timeout) is enforced in the loop itself,
* and every refusal carries its recovery instruction in the error text.
*
* @param definition - Agent definition (+ optional structured extraction)
* @param options - Registration options (name, caps, depth, pool, leg)
* @returns The registered tool name
* @see {@link AgentToolRegistrationOptions}
*/
registerAgentTool(definition: IsolatedAgentDefinition, options?: AgentToolRegistrationOptions): Promise<{
name: string;
}>;
/**
* Register the task CHECKLIST toolset — `tasks_create`, `tasks_update`,
* `tasks_list` — on this instance (TodoWrite-style planning for a
* long-running run). Opt-in and idempotent: existing callers see no new
* tools until they ask for them.
*
* The checklist is session state, not conversation state: it lives outside
* the message list, so summarization/compaction cannot lose it, and every
* tool result returns the whole list so the model re-anchors for free after
* a compaction. Read the same state from host code with
* {@link getTaskState} — that is all a completeness gate needs.
*
* Sessions come from the tool execution context. Call
* `setToolContext({ sessionId })` (or run the agent through
* `runIsolatedAgent`, which stamps one) so the checklist has a stable
* identity; a model tool call with no session anywhere falls back to a
* single default checklist per instance rather than one per call. A DIRECT
* `executeTool("tasks_create", …)` call should pass
* `authContext: { sessionId }` — the tool registry otherwise mints a fresh
* id for that one call.
*
* @see {@link getTaskState} for the host-side read
*/
registerTaskTools(): void;
/**
* Read a session's task checklist — synchronous, so a completeness gate is
* one line of host code:
* `getTaskState(id).items.filter(i => i.status === "pending")`.
*
* Never throws: an unknown session simply has an empty checklist. Omit
* `sessionId` to read the session the tools would currently write to (the
* instance's tool-context session, or its default checklist).
*/
getTaskState(sessionId?: string): ChecklistState;
/**
* Drop a session's checklist. Returns whether there was one to drop.
* Omit `sessionId` to clear the session the tools currently write to.
*/
clearTaskState(sessionId?: string): boolean;
/**
* Register the background-delegation toolset — `delegate_task` and
* `collect_results` — on this instance. Opt-in and idempotent: existing
* callers see no new tools until they ask for them.
*
* Delegation through {@link registerAgentTool} is synchronous — the loop
* blocks on each worker. These tools make it asynchronous: `delegate_task`
* returns a `workerId` at once and the agent keeps working, then
* `collect_results` claims whichever worker finished FIRST. Concurrency is
* bounded by the same process-wide pool `registerAgentTool` uses (raised,
* never lowered, by `maxConcurrent`), each worker's FULL report is banked to
* a file via {@link bankArtifact}, and the outstanding counts ride along in
* every `tasks_list` result so the agent learns a worker landed without
* polling.
*
* @param options - Depth ceiling, pool raise, and queue wait
* @see {@link spawnDelegate} for the host-side spawn
* @see {@link collectDelegates} for the host-side collect
*/
registerDelegationTools(options?: DelegateRegistrationOptions): void;
/**
* Start a background worker and get its handle immediately — before it has
* run anything, and long before it finishes.
*
* The worker runs through {@link runIsolatedAgent}: a fresh session on a
* worker instance sharing THIS instance's tool registry (so live MCP
* connections are reused), waste detection, honest stop reasons. Its
* complete report is banked when it settles; the outcome you collect carries
* a bounded summary plus the read-back call for the rest.
*
* @example
* ```typescript
* const a = await neurolink.spawnDelegate({ task: "Audit the auth changes" });
* const b = await neurolink.spawnDelegate({ task: "Review the migrations" });
* // …keep working…
* const first = await neurolink.collectDelegates({ mode: "any" });
* ```
*
* @param options - Task, scope, context, tool allowlist, budgets
* @returns The worker id, spawn time, and whether it is queued for a slot
* @throws when the task is empty or the caller is at the depth ceiling
*/
spawnDelegate(options: DelegateSpawnOptions): Promise<DelegateHandle>;
/**
* Claim finished background workers — in COMPLETION order, which has nothing
* to do with spawn order. Each outcome is handed out exactly once.
*
* @param request - `{ mode: "any" | "all" }` or `{ workerId }`, plus `waitMs`
* @returns Claimed outcomes plus what is still pending/ready
*/
collectDelegates(request: DelegateCollectRequest): Promise<DelegateCollectResult>;
/**
* Cancel background workers: one by id, or every outstanding worker this
* instance spawned. Cancelled workers still settle into a claimable outcome
* saying so.
*
* @param workerId - Cancel just this worker; omit to cancel all
* @returns How many workers were cancelled
*/
cancelDelegates(workerId?: string): Promise<number>;
/**
* This instance's artifact store, created on first use.
*
* Until now a store existed only when `mcp.outputLimits.strategy` was set to
* `"externalize"`, so a caller that just wanted to bank a worker report had
* to configure MCP output limits it did not use. This creates one on demand
* and registers `retrieve_context` alongside it, so a banked payload is
* readable by the model, not only by host code.
*
* Already-configured instances get the store they already had — the MCP
* output normalizer and banking deliberately share one store, so an
* externalized tool output and a banked report read back the same way.
*
* @returns The artifact store backing {@link bankArtifact} / {@link readArtifact}
*/
getArtifactStore(): ArtifactStore;
/**
* Replace this instance's artifact store.
*
* Everything that writes or reads artifacts follows the swap: banking,
* `retrieve_context`, host-side `readArtifact`, and the MCP output
* normalizer — which is rebuilt here because it captured the previous store
* at construction. Assigning the field alone would miss it, and
* externalized tool outputs would keep landing in the old backend while
* read-backs looked in the new one.
*
* Call it before the first bank or externalized tool output: artifacts
* already in the previous store are not migrated, and their ids stop
* resolving through this instance. `artifacts.store` in the constructor
* config is the same thing without the ordering concern.
*
* Ownership: a store you hand in — here or via `artifacts.store` — stays
* yours to close. NeuroLink closes only the stores it built itself, when
* they are replaced here and on `shutdown()`.
*
* @param store - Any {@link ArtifactStore}
*/
setArtifactStore(store: ArtifactStore): void;
/**
* Build the store the `artifacts` config (or `STORAGE_TYPE`) asks for. The
* Redis connection falls back to conversation memory's, so one
* `STORAGE_TYPE=redis` moves sessions and artifacts together.
*/
private createConfiguredArtifactStore;
/**
* (Re)build the MCP output normalizer over `artifactStore`. A no-op unless
* `mcp.outputLimits` was configured — without it nothing is externalized.
*/
private installOutputNormalizer;
/**
* Bank a payload to a file and get back a pointer to it.
*
* The payload is stored WHOLE. What you put in the conversation is the
* returned `preview` (a bounded head slice) and `readBackHint` (the literal
* `retrieve_context` call that fetches the rest) — so a 4 MB worker report
* costs a few hundred tokens of context and loses nothing, and compaction
* can drop the preview without destroying evidence.
*
* @example
* ```typescript
* const ref = await neurolink.bankArtifact(fullReport, {
* kind: "worker-report",
* label: "delegate:auth-review",
* sessionId: "review-1421",
* });
* // Hand the model ref.preview + ref.readBackHint, never fullReport.
* ```
*
* @param payload Complete text or JSON. Never truncated.
* @param options `kind` and `label` are required; see {@link BankArtifactOptions}
* @returns Id, bounded preview, byte size, and the read-back call
*/
bankArtifact(payload: string, options: BankArtifactOptions): Promise<BankedArtifactRef>;
/**
* Read a banked payload back from host code — the programmatic twin of the
* model's `retrieve_context({ artifactId })` call.
*
* Omit `page` for the complete payload; pass `{ offset, limit }` to walk a
* large one in windows. Returns null when the id is unknown or expired.
*
* @param id `artifactId` from a {@link BankedArtifactRef}
* @param page Optional character window
*/
readArtifact(id: string, page?: ArtifactPageRequest): Promise<string | null>;
/**
* Register the background-command toolset — `run_command_bg`,
* `command_status`, `command_output`, `command_kill` — on this instance, and
* declare what may be executed. Opt-in and idempotent: existing callers see
* no new tools until they ask for them.
*
* A reviewing agent needs to run real commands — a build, a test suite, a
* linter whose output is the evidence for a finding — without blocking its
* own loop and without losing a byte of what they printed. These tools start
* a command detached, write both streams to files as they arrive, and bank
* the COMPLETE files as artifacts when the command settles; the conversation
* gets a bounded tail plus the read-back call.
*
* The policy is not optional. `allowedExecutables` is matched exactly
* against `argv[0]`, `cwdRoot` is a realpath-checked sandbox, there is never
* a shell, and a command that outlives `defaultTimeoutMs` is killed.
*
* @param policy - What may run, where, for how long, and how loudly
* @see {@link startBackgroundCommand} for the host-side start
* @see {@link registerGitTools} for read-only git without a general policy
*/
registerBackgroundCommandTools(policy: BackgroundCommandPolicy): void;
/**
* Declare (or replace) what this instance may execute, without registering
* the model-facing tools. Host code that only drives
* {@link startBackgroundCommand} itself needs nothing more than this.
*
* @param policy - What may run, where, for how long, and how loudly
*/
setBackgroundCommandPolicy(policy: BackgroundCommandPolicy): void;
/**
* Start a command in the background and get its task id immediately.
*
* @example
* ```typescript
* const { taskId } = await neurolink.startBackgroundCommand(
* ["pnpm", "run", "lint"],
* { cwd: repoRoot },
* );
* // …keep working…
* const status = await neurolink.awaitBackgroundCommand(taskId);
* const full = await neurolink.readArtifact(status.stdout!.artifactId);
* ```
*
* @param argv - Executable first, one entry per argument. Never a command string.
* @param options - cwd (sandboxed), timeout, byte cap, env, label, session
* @returns The task id, the argv that ran, and when it started
* @throws when no policy is set, argv is malformed, the executable is not
* allowlisted, the policy vetoes it, or the cwd escapes the sandbox
*/
startBackgroundCommand(argv: string[], options: BackgroundCommandOptions): Promise<BackgroundCommandHandle>;
/**
* Everything known about one command right now — synchronous, so a
* mid-loop monitor costs nothing.
*
* @param taskId - Task id from {@link startBackgroundCommand}
* @throws when the task id is unknown to this instance
*/
getBackgroundCommandStatus(taskId: string): BackgroundCommandStatus;
/**
* Wait for a command to settle. `timeoutMs` bounds the WAIT, not the
* command: when it elapses the current status is returned rather than
* thrown, so a caller can poll in bounded steps and never lose the job.
*
* @param taskId - Task id from {@link startBackgroundCommand}
* @param opts - `timeoutMs` to bound the wait
*/
awaitBackgroundCommand(taskId: string, opts?: {
timeoutMs?: number;
}): Promise<BackgroundCommandStatus>;
/**
* Kill a running command — SIGTERM, then SIGKILL five seconds later — and
* resolve with its settled status. Whatever it printed first is still
* banked: killing a command discards the process, never its output.
*
* @param taskId - Task id from {@link startBackgroundCommand}
* @param signal - Signal to send first. Default SIGTERM
*/
killBackgroundCommand(taskId: string, signal?: NodeJS.Signals): Promise<BackgroundCommandStatus>;
/**
* Read one character window of a command's output straight from its log
* file — while it is still running, or long after it finished. Offsets,
* `totalSize` and `hasMore` match `retrieve_context` exactly.
*
* @param taskId - Task id from {@link startBackgroundCommand}
* @param page - Which stream, and which window of it
*/
readBackgroundCommandOutput(taskId: string, page: BackgroundCommandPageRequest): Promise<BackgroundCommandOutputPage>;
/**
* Register the read-only git toolset — `git_log`, `git_show`, `git_diff`,
* `git_blame`, `git_merge_base`, `git_ls_files` — on this instance. Opt-in
* and idempotent.
*
* These are BOUNDED tools, not a shell: the model supplies values (a ref, a
* path, a line range), never flags, and each tool assembles a fixed argv
* from them. That is what keeps them read-only — a free-form argument string
* would carry `--output=<file>` and `diff.external` straight through.
*
* Registering them widens nothing else: they run under a private
* one-executable policy rooted at `repoRoot`, so `run_command_bg` still
* cannot execute git, and no general command policy is required.
*
* @param options - Repository root, plus timeout / byte-cap / preview bounds
*/
registerGitTools(options: GitToolsetOptions): void;
/**
* Run one read-only git command from host code, with the same bounding the
* tools get: the complete stdout is banked, the result carries a preview and
* the read-back call.
*
* @param args - Git arguments, e.g. `["log", "--oneline"]`. Assembled by the
* caller, which is responsible for every value in them
* @param sessionId - Session the command belongs to
* @throws when {@link registerGitTools} has not been called
*/
runGitCommand(args: string[], sessionId?: string): Promise<GitToolResult>;
/**
* Execute an agent network with the given input.
*
* @param network - The agent network to execute
* @param input - Execution input (message and context)
* @param options - Optional execution options
* @returns Network execution result with content, trace, and usage
*
* @see {@link NetworkExecutionInput} for input options
* @see {@link NetworkExecutionResult} for result structure
* @since 8.38.0
*/
executeNetwork(network: import("./agent/agentNetwork.js").AgentNetwork, input: NetworkExecutionInput, options?: NetworkExecutionOptions): Promise<NetworkExecutionResult>;
/**
* Stream agent network execution with real-time events.
*
* @param network - The agent network to stream
* @param input - Execution input (message and context)
* @param options - Optional execution options
* @returns Async iterable of network stream chunks
*
* @see {@link NetworkStreamChunk} for chunk types
* @since 8.38.0
*/
streamNetwork(network: import("./agent/agentNetwork.js").AgentNetwork, input: NetworkExecutionInput, options?: NetworkExecutionOptions): AsyncIterable<NetworkStreamChunk>;
/**
* Create a NetworkOrchestrator for managing multiple agent networks.
*
* @param config - Orchestrator configuration options
* @returns A new NetworkOrchestrator instance
* @since 8.38.0
*/
createOrchestrator(config?: import("./types/index.js").OrchestratorConfig): Promise<import("./agent/orchestration/index.js").NetworkOrchestrator>;
/**
* Create an AgentCoordinator for managing agent coordination strategies.
*
* @param config - Coordinator configuration options
* @returns A new AgentCoordinator instance
* @since 8.38.0
*/
createCoordinator(config?: import("./types/index.js").CoordinatorConfig): Promise<import("./agent/coordination/index.js").AgentCoordinator>;
/**
* Create a MessageBus for inter-agent communication.
*
* @param config - Message bus configuration options
* @returns A new MessageBus instance
* @since 8.38.0
*/
createMessageBus(config?: import("./types/index.js").MessageBusConfig): Promise<import("./agent/communication/index.js").MessageBus>;
/**
* Dispose of all resources and cleanup connections
* Call this method when done using the NeuroLink instance to prevent resource leaks
* Especially important in test environments where multiple instances are created
*/
dispose(): Promise<void>;
/**
* Get the tool registry instance
* Used internally by server adapters for tool management
* @returns The MCPToolRegistry instance
*/
getToolRegistry(): MCPToolRegistry;
/**
* Manually trigger context compaction for a session.
* Runs the full 4-stage compaction pipeline.
*/
compactSession(sessionId: string, config?: CompactionConfig): Promise<CompactionResult | null>;
/**
* Get context usage statistics for a session.
* Returns token counts, usage ratio, and breakdown by category.
*/
getContextStats(sessionId: string, provider?: string, model?: string): Promise<{
estimatedInputTokens: number;
availableInputTokens: number;
usageRatio: number;
shouldCompact: boolean;
messageCount: number;
} | null>;
/**
* Check if a session needs compaction.
*/
needsCompaction(sessionId: string, provider?: string, model?: string): boolean;
/**
* Set the authentication provider for the NeuroLink instance
*
* @param config - Auth provider or configuration to create one
*/
setAuthProvider(config: NeuroLinkAuthConfig): Promise<void>;
private initializeAuthProviderFromConfig;
/**
* Get the currently configured authentication provider
*/
getAuthProvider(): AuthProvider | undefined;
/**
* Lazily initialize the auth provider from pendingAuthConfig.
* Called on first use (generate/stream with auth token) to avoid
* async work in the synchronous constructor.
*/
private ensureAuthProvider;
/**
* Set the current authentication context for request handling.
*
* Delegates to the global AuthContextHolder so that auth state is NOT
* stored as an instance field (which would leak between concurrent requests
* sharing the same NeuroLink singleton). Prefer `runWithAuthContext()` from
* `authContext.ts` for proper request-scoped context via AsyncLocalStorage.
*
* @param context - The authenticated user context
*/
setAuthContext(context: AuthenticatedContext): Promise<void>;
/**
* Get the current authentication context.
*
* Checks AsyncLocalStorage first, then falls back to the global holder.
*/
getAuthContext(): Promise<AuthenticatedContext | undefined>;
/**
* Clear the current authentication context
*/
clearAuthContext(): Promise<void>;
/**
* Get the external server manager instance
* Used internally by server adapters for external MCP server management
* @returns The ExternalServerManager instance
*/
getExternalServerManager(): ExternalServerManager;
private buildResolutionContext;
/**
* Resolve dynamic arguments in GenerateOptions, mutating the options in place.
* Only resolves fields that are functions; static values pass through unchanged.
*/
private resolveDynamicOptions;
private resolveDynamicFields;
}
export declare const neurolink: NeuroLink;
export default neurolink;