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

@project65/storyframe

v0.3.7

Published

Agent framework for building AI-powered applications with support for OpenAI, tool usage, and persistent storage

Downloads

73

Readme

@project65/storyframe

npm version License: MIT TypeScript

@project65/storyframe is a powerful and flexible TypeScript framework designed to accelerate the development of sophisticated AI-powered applications. It provides a structured, extensible architecture for building and managing AI agents that can interact with users, leverage external tools, and maintain conversational context.

Whether you're building a simple chatbot, a complex multi-agent system, or an AI-driven data analysis tool, Storyframe provides the essential components to get you started quickly.

📚 Table of Contents

✨ Features

  • 🤖 Flexible Agent System: Abstract base classes and a ready-to-use OpenAIAgent.
  • 🔧 Extensible Tool Framework: Define custom tools to give your agents new capabilities.
  • 💾 Persistent Storage: Includes in-memory and Supabase adapters.
  • 🔄 First-Class Streaming Support: Built-in support for streaming responses.
  • 📝 TypeScript-First: Fully written in TypeScript for robust, type-safe code.
  • 🛠️ Comprehensive Callback System: For monitoring, logging, and debugging.
  • 🎯 Custom Response Formats: Define your own response schemas using Zod.
  • 📋 Templated System Prompts: Dynamic system prompts with variable support.

🏗️ Architecture

Storyframe's architecture is designed to be modular and intuitive. The diagram below illustrates how the core components work together.

graph TD
    subgraph Legend
        direction LR
        box1[ ] -.- UserInput[User Input]
        box2[ ] -.- YourCode[Your Code]
        box3[ ] -.- Framework[Storyframe]
        box4[ ] -.- External[External]
    end

    subgraph "Your Application"
        UserInput -- "sends" --> YourCode
        YourCode -- "receives" --> ResponseStream
    end

    subgraph "Storyframe Framework"
        Agent -- "manages" --> ToolRouter
        Agent -- "uses" --> ChatStorage
        ToolRouter -- "notifies" --> Callbacks
    end
    
    subgraph "External Services"
        LLM[LLM e.g. OpenAI]
        DB[(Database e.g. Supabase)]
        CustomTool[Custom Tool]
    end

    YourCode -- "instantiates and calls" --> Agent
    
    Agent -- "communicates with" --> LLM
    LLM -- "responds to" --> Agent
    Agent -- "streams to" --> ResponseStream

    ToolRouter -- "executes" --> CustomTool
    ChatStorage -- "persists to" --> DB
    
    classDef default fill:#fff,stroke:#333,stroke-width:2px;
    classDef userInput fill:#f9f,stroke:#333,stroke-width:1px;
    classDef yourCode fill:#ccf,stroke:#333,stroke-width:1px;
    classDef framework fill:#cfc,stroke:#333,stroke-width:1px;
    classDef external fill:#ffc,stroke:#333,stroke-width:1px;

    class box1,UserInput userInput;
    class box2,YourCode,ResponseStream yourCode;
    class box3,Agent,ToolRouter,ChatStorage,Callbacks framework;
    class box4,LLM,DB,CustomTool external;

Core Concepts

  • Agents: The brain of your application. An agent orchestrates the conversation, calls tools, and interacts with the language model. OpenAIAgent is the primary implementation.
  • Tools: Functions that an agent can execute. Tools are how you connect your agent to external data sources, APIs, or custom logic. The ToolRouter manages and exposes these tools to the agent.
  • Storage: The memory of your application. Chat storage adapters like InMemoryChatStorage or SupabaseChatStorage are used to load and save conversation histories.
  • Callbacks: Hooks into the lifecycle of tool execution. Use callbacks to implement logging, performance monitoring, or custom error handling.

🚀 Quick Start

Installation

npm install @project65/storyframe
# or
yarn add @project65/storyframe
# or
pnpm add @project65/storyframe

Basic Usage

import { OpenAIAgent, InMemoryChatStorage } from '@project65/storyframe';

const agent = new OpenAIAgent({
  name: 'my-assistant',
  id: 'agent-1',
  description: 'A helpful assistant',
  apiKey: process.env.OPENAI_API_KEY,
  model: 'gpt-4',
  streaming: true
});

const storage = new InMemoryChatStorage();
const response = await agent.process('Hello!', 'user-1', 'session-1', []);

To run the same agent against Groq’s OpenAI-compatible endpoint, switch the provider and key—Storyframe updates the base URL automatically:

const agent = new OpenAIAgent({
  name: 'groq-assistant',
  id: 'agent-groq',
  description: 'Runs on Groq',
  inferenceProvider: 'groq',
  apiKey: process.env.GROQ_API_KEY,
  model: 'mixtral-8x7b-32768'
});

📖 Advanced Usage

Custom Response Formats

Storyframe v0.2.0 introduces custom response formats using Zod schemas. This allows you to define exactly how you want your agent's responses to be structured:

import { z } from 'zod';
import { OpenAIAgent } from '@project65/storyframe';

// Define a custom response schema
const CustomResponseSchema = z.object({
  messages: z.array(z.object({
    type: z.enum(['text', 'code', 'image']),
    content: z.string(),
    metadata: z.record(z.string()).optional()
  })),
  confidence: z.number().min(0).max(1).optional()
});

// Create an agent with the custom format
const agent = new OpenAIAgent({
  name: 'structured-agent',
  id: 'agent-1',
  description: 'An agent with structured outputs',
  apiKey: process.env.OPENAI_API_KEY,
  model: 'gpt-4',
  textFormat: {
    schema: CustomResponseSchema,
    field: 'messages'  // Root field in your schema
  }
});

Custom System Prompts

Use dynamic system prompts with variable substitution:

const agent = new OpenAIAgent({
  // ... other options ...
  customSystemPrompt: {
    template: `You are {{role}}, specialized in {{domain}}.
Your task is to {{mainTask}}.
Follow these guidelines:
{{#each guidelines}}
- {{this}}
{{/each}}`,
    variables: {
      role: "an AI coding assistant",
      domain: "TypeScript development",
      mainTask: "help users write clean, maintainable code",
      guidelines: [
        "Always explain your reasoning",
        "Suggest best practices",
        "Consider performance implications"
      ]
    }
  }
});

Optional LLM Analytics with PostHog

Storyframe can emit PostHog LLM analytics without impacting existing users. Provide your PostHog project credentials when constructing the agent, then add analytics metadata on each request as needed.

import { OpenAIAgent } from '@project65/storyframe';

const agent = new OpenAIAgent({
  name: 'instrumented-agent',
  id: 'agent-analytics',
  description: 'An agent with optional analytics',
  apiKey: process.env.OPENAI_API_KEY!,
  posthog: {
    apiKey: process.env.POSTHOG_API_KEY!,
    host: 'https://us.i.posthog.com' // or your EU/self-hosted endpoint
  }
});

const response = await agent.process(
  'Summarize this conversation.',
  'user-123',
  'session-456',
  chatHistory,
  undefined,
  {
    posthogDistinctId: 'user-123',
    posthogTraceId: 'conversation-456',
    posthogProperties: { plan: 'pro', workspaceId: 'acme-co' },
    posthogPrivacyMode: false
  }
);

// On shutdown, flush PostHog if Storyframe created the client
await agent.shutdown();

If you supply your own OpenAI or PostHog clients, simply pass them through the constructor and Storyframe will reuse them.

Heads up: PostHog’s official AI instrumentation requires Node.js 20+. If you are on an older runtime, Storyframe will automatically fall back to the standard OpenAI client and skip analytics.

Persistent Storage with Supabase

Set up Supabase storage for production use:

import { SupabaseChatStorage } from '@project65/storyframe';

// First, create your Supabase table
/*
CREATE TABLE public.chat_history (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
  user_id TEXT NOT NULL,
  session_id TEXT NOT NULL,
  agent_id TEXT NOT NULL,
  message JSONB NOT NULL,
  message_key TEXT,
  message_index BIGINT
);
*/

const storage = new SupabaseChatStorage(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!
);

// Use the storage with your agent
const history = await storage.fetchChat(userId, sessionId, agentId);
await storage.saveChat(userId, sessionId, agentId, newMessage);

Tool Development

Create custom tools with type-safe parameters:

import { Tool, ToolRouter } from '@project65/storyframe';

const weatherTool = new Tool({
  name: 'getWeather',
  description: 'Get weather information for a location',
  properties: {
    location: {
      type: 'string',
      description: 'City name or coordinates'
    },
    units: {
      type: 'string',
      enum: ['celsius', 'fahrenheit']
    }
  },
  required: ['location'],
  handler: async ({ location, units = 'celsius' }) => {
    // Implement weather lookup logic
    return { temperature: 22, units, location };
  }
});

const router = new ToolRouter();
router.register(weatherTool);

Monitoring & Callbacks

Implement comprehensive monitoring:

import { 
  CompositeCallbacks, 
  LoggingCallbacks, 
  PerformanceCallbacks 
} from '@project65/storyframe';

class CustomCallbacks extends LoggingCallbacks {
  onToolStart(toolName: string, input: any) {
    console.log(`[${new Date().toISOString()}] Starting ${toolName}`);
    // Send metrics to your monitoring system
  }
}

const callbacks = new CompositeCallbacks(
  new CustomCallbacks(),
  new PerformanceCallbacks()
);

const router = new ToolRouter(callbacks);

📘 API Reference

For detailed API documentation, see our API Reference.

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

📄 License

This project is licensed under the MIT License.