npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aeye/core

v0.3.2

Published

@aeye Core - Core primitives for AI components (agents, tools, prompts)

Readme

@aeye/core

Core primitives for building AI agents, tools, and prompts with TypeScript. Provides a type-safe, composable framework for creating sophisticated AI applications with structured inputs/outputs, tool calling, and context management.

Features

  • 🎯 Type-Safe Components - Prompts, Tools, and Agents with full TypeScript support
  • 🔧 Tool Calling - Native support for function/tool calling with schema validation
  • 📝 Template-Based Prompts - Handlebars templates for dynamic prompt generation
  • ✅ Schema Validation - Zod integration for structured inputs and outputs
  • 🌊 Streaming Support - First-class streaming for real-time AI responses
  • 🔄 Composable Architecture - Tools can use other tools, prompts can use tools, agents orchestrate everything
  • 📊 Context Management - Type-safe context threading and automatic token window management
  • 🎛️ Flexible Execution - Sequential, parallel, or immediate tool execution modes

Installation

npm install @aeye/core zod handlebars

Core Concepts

Components

All AI primitives implement the Component interface:

  • Prompt - Generates AI responses with optional tool usage and structured outputs
  • Tool - Extends AI capabilities with custom functions and external integrations
  • Agent - Orchestrates complex workflows combining prompts and tools

Context & Metadata

  • Context (TContext) - Application-specific data threaded through operations (user, db, etc.)
  • Metadata (TMetadata) - Execution settings for AI requests (model, temperature, etc.)

Quick Start

Basic Prompt

import { Prompt } from '@aeye/core';
import z from 'zod';

const summarizer = new Prompt({
  name: 'summarize',
  description: 'Summarizes text concisely',
  content: 'Summarize the following text:\n\n{{text}}',
  
  // Transform input
  input: (input: { text: string }) => ({ text: input.text }),
  
  // Define output schema
  schema: z.object({
    summary: z.string().describe('A concise summary'),
    keyPoints: z.array(z.string()).describe('Main points')
  })
});

// Execute with a context that has an executor
const result = await summarizer.get(
  'result',
  { text: 'Long article text...' },
  {
    execute: yourAIExecutor, // Executor function from a provider
    messages: []
  }
);

console.log(result.summary);
console.log(result.keyPoints);

Creating Tools

import { Tool } from '@aeye/core';
import z from 'zod';

const weatherTool = new Tool({
  name: 'getWeather',
  description: 'Get current weather for a location',
  instructions: 'Use this tool to get weather data',
  
  schema: z.object({
    location: z.string().describe('City name or coordinates'),
    units: z.enum(['celsius', 'fahrenheit']).default('celsius')
  }),
  
  call: async (input, refs, ctx) => {
    const response = await fetch(
      `https://api.weather.com/v1/${input.location}`
    );
    const data = await response.json();
    
    return {
      temperature: data.temp,
      condition: data.condition,
      humidity: data.humidity
    };
  }
});

Prompts with Tools

const travelAdvisor = new Prompt({
  name: 'travelAdvisor',
  description: 'Provides travel advice based on weather',
  content: `You are a travel advisor. Help plan a trip to {{destination}}.
  
Use the weather tool to check current conditions, then provide:
- What to pack
- Activities to do
- Best times to visit`,
  
  input: (input: { destination: string }) => ({ 
    destination: input.destination 
  }),
  
  tools: [weatherTool],
  
  schema: z.object({
    recommendations: z.array(z.string()),
    packingList: z.array(z.string()),
    weatherNotes: z.string()
  })
});

// The AI will automatically call weatherTool if needed
const advice = await travelAdvisor.get(
  'result',
  { destination: 'Paris' },
  { execute: yourAIExecutor, messages: [] }
);

Streaming Responses

// Stream content only
for await (const chunk of summarizer.get(
  'streamContent',
  { text: 'Long text...' },
  { stream: yourAIStreamer, messages: [] }
)) {
  process.stdout.write(chunk);
}

// Stream all events (including tool calls)
for await (const event of summarizer.get(
  'stream',
  { text: 'Long text...' },
  { stream: yourAIStreamer, messages: [] }
)) {
  if (event.type === 'textPartial') {
    console.log('Text:', event.content);
  } else if (event.type === 'toolStart') {
    console.log('Tool started:', event.tool.name);
  } else if (event.type === 'toolOutput') {
    console.log('Tool result:', event.result);
  }
}

Building Agents

import { Agent } from '@aeye/core';

const researchAgent = new Agent({
  name: 'researcher',
  description: 'Conducts research on topics',
  
  refs: [searchTool, summarizeTool, analyzeTool],
  
  call: async (input: { topic: string }, [search, summarize, analyze], ctx) => {
    // Step 1: Search for information
    const searchResults = await search.run(
      { query: input.topic, limit: 5 },
      ctx
    );
    
    // Step 2: Summarize findings
    const summaries = [];
    for (const result of searchResults) {
      const summary = await summarize.get(
        'result',
        { text: result.content },
        ctx
      );
      summaries.push(summary);
    }
    
    // Step 3: Analyze and synthesize
    const analysis = await analyze.get(
      'result',
      { topic: input.topic, sources: summaries },
      ctx
    );
    
    return analysis;
  }
});

const research = await researchAgent.run(
  { topic: 'Quantum Computing' },
  { execute: yourAIExecutor, messages: [] }
);

Prompt Modes

The get method supports different execution modes:

| Mode | Description | Returns | |------|-------------|---------| | 'result' | Get final structured output | TOutput | | 'tools' | Get tool call results | PromptToolOutput[] | | 'stream' | Stream all events | AsyncGenerator<PromptEvent> | | 'streamTools' | Stream tool outputs | AsyncGenerator<PromptToolOutput> | | 'streamContent' | Stream text content only | AsyncGenerator<string> |

// Get structured result
const result = await prompt.get('result', input, ctx);

// Get tool outputs only
const tools = await prompt.get('tools', input, ctx);

// Stream everything
for await (const event of prompt.get('stream', input, ctx)) {
  // Handle different event types
}

Tool Execution Modes

Control how tools are executed:

const prompt = new Prompt({
  name: 'multi-tool',
  description: 'Uses multiple tools',
  content: 'Analyze the data',
  tools: [tool1, tool2, tool3],
  
  // Tool execution mode
  toolExecution: 'parallel', // 'sequential' | 'parallel' | 'immediate'
  
  // Retry configuration
  toolRetries: 2,         // Retry failed tools
  toolIterations: 3,      // Max iterations for tool calls
  toolsMax: 5,           // Max total tool calls
});
  • sequential - Wait for each tool to finish before continuing
  • parallel - Start all tools at once, wait for all to complete
  • immediate - Start tools immediately as they're available

Advanced Features

Context-Aware Tools

const contextTool = new Tool({
  name: 'contextAware',
  description: 'A context-aware tool',
  instructions: 'Use this tool...',
  
  // Schema can depend on context
  schema: (ctx) => {
    if (ctx.userRole === 'admin') {
      return z.object({
        action: z.enum(['read', 'write', 'delete'])
      });
    }
    return z.object({
      action: z.enum(['read'])
    });
  },
  
  // Check if tool is applicable
  applicable: (ctx) => {
    return ctx.isAuthenticated === true;
  },
  
  call: async (input, refs, ctx) => {
    // Implementation with context access
  }
});

Custom Validation

const validatedTool = new Tool({
  name: 'placeOrder',
  description: 'Place an order',
  
  schema: z.object({
    itemId: z.string(),
    quantity: z.number().min(1)
  }),
  
  // Additional validation beyond schema
  validate: async (input, ctx) => {
    const inventory = await checkInventory(input.itemId);
    if (inventory < input.quantity) {
      throw new Error(`Only ${inventory} items available`);
    }
  },
  
  call: async (input, refs, ctx) => {
    // Place order
  }
});

Event Tracking

import { withEvents } from '@aeye/core';

const runner = withEvents({
  onStatus: (instance) => {
    console.log(`${instance.component.name}: ${instance.status}`);
    if (instance.status === 'completed') {
      const duration = instance.completed - instance.started;
      console.log(`Took ${duration}ms`);
    }
  },
  
  onPromptEvent: (instance, event) => {
    if (event.type === 'usage') {
      console.log('Tokens used:', event.usage);
    }
  }
});

const result = await prompt.get(
  'result',
  { text: 'Hello' },
  {
    execute: yourAIExecutor,
    messages: [],
    runner
  }
);

Token Management

Automatic conversation trimming when token limits are reached:

const context = {
  execute: yourAIExecutor,
  messages: conversationHistory,
  
  // Token configuration
  defaultCompletionTokens: 2048,
  
  // Custom token estimation
  estimateTokens: (message) => {
    return message.content.length / 4; // Rough estimate
  }
};

// Prompt will automatically trim messages if needed
const result = await prompt.get('result', { text: 'Query' }, context);

Dynamic Reconfiguration

const adaptivePrompt = new Prompt({
  name: 'adaptive',
  description: 'Adapts based on execution stats',
  content: 'Solve: {{problem}}',
  schema: z.object({ solution: z.string() }),
  
  // Adjust configuration during execution
  reconfig: (stats, ctx) => {
    // If tools are failing, change strategy
    if (stats.toolCallErrors > 3) {
      return {
        config: { toolsOneAtATime: true },
        maxIterations: 2
      };
    }
    return {};
  }
});

API Reference

Prompt

class Prompt<TContext, TMetadata, TName, TInput, TOutput, TTools>

Constructor Options:

  • name: string - Unique identifier
  • description: string - Purpose description
  • content: string - Handlebars template
  • input?: Function - Transform input for template
  • schema?: ZodType | Function - Output schema
  • config?: Partial<Request> | Function - AI request config
  • tools?: Tool[] - Available tools
  • toolExecution?: 'sequential' | 'parallel' | 'immediate'
  • toolRetries?: number - Retry failed tools
  • toolIterations?: number - Max tool call iterations
  • outputRetries?: number - Retry invalid outputs
  • metadata?: TMetadata - Execution metadata
  • validate?: Function - Post-validation hook
  • applicable?: Function - Applicability check

Methods:

  • get(input, mode, ctx) - Execute and retrieve output
  • run(input, ctx) - Execute with full streaming
  • applicable(ctx) - Check if prompt can run

Tool

class Tool<TContext, TMetadata, TName, TParams, TOutput, TRefs>

Constructor Options:

  • name: string - Unique identifier
  • description: string - Purpose description
  • instructions?: string - Handlebars usage instructions
  • schema: ZodType | Function - Input schema
  • refs?: Component[] - Referenced components
  • call: Function - Implementation
  • validate?: Function - Post-validation hook
  • applicable?: Function - Applicability check

Methods:

  • run(input, ctx) - Execute the tool
  • compile(ctx) - Generate tool definition for AI
  • parse(ctx, args) - Parse and validate arguments
  • applicable(ctx) - Check if tool can run

Agent

class Agent<TContext, TMetadata, TName, TInput, TOutput, TRefs>

Constructor Options:

  • name: string - Unique identifier
  • description: string - Purpose description
  • refs?: Component[] - Referenced components
  • call: Function - Implementation with refs
  • applicable?: Function - Applicability check

Methods:

  • run(input, ctx) - Execute the agent
  • applicable(ctx) - Check if agent can run

Context Structure

interface Context<TContext, TMetadata> {
  // Required: AI execution
  execute?: Executor<TContext, TMetadata>;
  stream?: Streamer<TContext, TMetadata>;
  
  // Messages
  messages: Message[];
  
  // Token management
  defaultCompletionTokens?: number;
  estimateTokens?: (message: Message) => number;
  
  // Execution control
  signal?: AbortSignal;
  runner?: Events;
  
  // User context (TContext) spreads here
  [key: string]: any;
}

Request Configuration

interface Request {
  messages: Message[];
  temperature?: number;      // 0-2
  topP?: number;            // 0-1
  maxTokens?: number;
  stop?: string | string[];
  tools?: ToolDefinition[];
  toolChoice?: 'auto' | 'required' | 'none' | { tool: string };
  responseFormat?: 'text' | 'json' | ResponseFormat;
  // ... more options
}

Best Practices

  1. Type Safety - Use TypeScript generics for context and metadata
  2. Schema Validation - Use Zod for robust input/output validation
  3. Error Handling - Wrap component execution in try-catch blocks
  4. Token Management - Provide estimateTokens for accurate trimming
  5. Tool Organization - Group related tools in agents
  6. Testing - Unit test tools and prompts independently
  7. Documentation - Document template variables and schemas
  8. Context Minimization - Pass only necessary data in context
  9. Streaming - Use streaming for better UX with long responses
  10. Validation - Use validate hooks for business logic validation

Examples

See the src/__tests__ directory for comprehensive examples:

  • prompt-core-features.test.ts - Basic prompt usage
  • prompt-streaming-tool-events.test.ts - Streaming and events
  • tool.test.ts - Tool creation and usage
  • agent.test.ts - Agent orchestration
  • context-propagation.test.ts - Context handling

TypeScript Support

Full type inference across component hierarchies:

// Types are automatically inferred
const prompt = new Prompt({
  name: 'example',
  schema: z.object({ result: z.string() })
  // ...
});

// TypeScript knows the output type
const output = await prompt.get({}); // { result: string }

// Tool parameters are type-safe
const tool = new Tool({
  name: 'math',
  schema: z.object({ a: z.number(), b: z.number() }),
  call: (input, refs, ctx) => {
    // input is typed as { a: number, b: number }
    return input.a + input.b;
  }
});

Contributing

Contributions are welcome! See the main @aeye repository for contribution guidelines.

License

GPL-3.0 © ClickerMonkey

Links