JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 252
  • Score
    100M100P100Q88110F
  • License GPL-3.0

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

Package Exports

  • @aeye/core
  • @aeye/core/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@aeye/core) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

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