Skip to content

Conversation

@mattapperson
Copy link
Collaborator

@mattapperson mattapperson commented Oct 30, 2025

Overview

This PR implements a comprehensive memory system for the OpenRouter TypeScript SDK, enabling conversation history management, thread-based conversations, and working memory for both threads and users/resources.

Recent Updates

100% Test Coverage Achieved ✅

Complete unit test suite with 153 passing tests:

  • 91 unit tests for InMemoryStorage (100% coverage)
  • 62 unit tests for Memory class (100% coverage)
  • 17 E2E integration tests
  • All edge cases covered including working memory hydration, tokenCount:0, and error conditions

Coverage Results:

  • memory.ts: 100% statements, 100% branch, 100% functions
  • in-memory.ts: 100% statements, 100% branch, 100% functions

Configuration Simplification ✨

Reduced MemoryConfig from 7 fields to 2 for a cleaner, simpler API:

  • Removed autoSave, autoInject, trackTokenUsage (now always enabled)
  • Removed unused strategy and retainRecentMessages fields
  • Improved error messages for unsupported storage operations
  • Replaced custom ID generation with UUID v4
  • Updated all examples and tests

Before:

const memory = new Memory(storage, {
  maxHistoryMessages: 10,
  autoInject: true,
  autoSave: true,
  trackTokenUsage: true,
  contextWindow: {
    maxTokens: 1000,
    strategy: "token-aware",
    retainRecentMessages: 5,
  },
});

After:

const memory = new Memory(storage, {
  maxHistoryMessages: 10,  // Optional, defaults to 10
  contextWindow: {          // Optional
    maxTokens: 1000,
  },
});

Features

Core Components

  • Memory: Main API class for managing threads, resources, messages, and working memory
  • MemoryStorage: Interface for pluggable storage implementations
  • InMemoryStorage: Default in-memory storage implementation (data lost on process exit)
  • Comprehensive Types: Full TypeScript type definitions for all entities

Automatic Memory Management

  • Auto-inject history: Automatically prepends conversation history to API requests when threadId is provided
  • Auto-save messages: Automatically saves both user input and assistant responses to memory
  • Token tracking: Automatically tracks token usage for all messages

Context-Aware Enhancements

  • Message Editing: Update messages with automatic version history tracking
  • Token Management: Track token counts and retrieve messages within token budgets
  • Cache Control: Manage message caching with expiration
  • Status Filtering: Filter messages by status (active, archived, deleted)
  • Importance Scoring: Prioritize messages by importance for token-aware selection
  • Clear Error Messages: Descriptive errors when storage doesn't support optional features

Thread & Resource Management

  • Create and manage conversation threads
  • Associate threads with resources (users)
  • Get all threads for a specific resource
  • Delete threads and their associated messages

Working Memory

  • Thread-scoped: Store context specific to a conversation (e.g., current topic, message count)
  • Resource-scoped: Store user preferences, profile data, etc.
  • Store arbitrary JSON-serializable data

Serialization & Persistence

  • Export entire memory state to JSON
  • Import/restore from serialized state
  • Export/import individual threads
  • Ready for file-based, database, or remote storage backends

Provider Compatibility

This memory system is fully compatible with hosted context management solutions as storage providers, including:

  • Claude Context Editing API: The memory interface maps directly to Claude's context editing API, supporting message editing, version history, cache control, and importance scoring
  • OpenAI Conversations API: Compatible with OpenAI's conversations API, enabling seamless integration with hosted conversation management
  • Custom Hosted Solutions: The MemoryStorage interface can be implemented to integrate with any hosted conversation or context management service

The optional enhancement methods (updateMessage, getMessageHistory, etc.) enable rich integration with provider-specific features while maintaining a provider-agnostic API. Storage implementations can choose which optional methods to support based on the provider's capabilities.

Usage Example

import { OpenRouter, Memory, InMemoryStorage } from "@openrouter/sdk";

// Create storage and memory instance with simplified config
const storage = new InMemoryStorage();
const memory = new Memory(storage, {
  maxHistoryMessages: 10,  // Optional, defaults to 10
});

// Create client with memory
const client = new OpenRouter({
  apiKey: process.env.OPENROUTER_API_KEY,
  memory,
});

// Use with automatic history management
const response = client.getResponse({
  model: "meta-llama/llama-3.2-1b-instruct",
  input: "My name is Alice",
  threadId: "thread-123",
  resourceId: "user-456"
});

const text = await response.text;
console.log(text);

// Follow-up message - history automatically injected and saved
const response2 = client.getResponse({
  model: "meta-llama/llama-3.2-1b-instruct",
  input: "What's my name?",
  threadId: "thread-123",
  resourceId: "user-456"
});

const text2 = await response2.text;
console.log(text2); // Should remember the name is Alice

// Token-aware memory management
const tokenMemory = new Memory(new InMemoryStorage(), {
  contextWindow: { maxTokens: 1000 }
});

const messagesInBudget = await tokenMemory.getMessagesWithinBudget("thread-123");

// Manage working memory
await memory.updateResourceWorkingMemory("user-456", {
  name: "Alice",
  preferences: { theme: "dark" },
});

// Serialize for persistence
const state = await storage.serialize();
// Save to file/database: fs.writeFileSync('memory.json', JSON.stringify(state));

// Later, restore from storage
const newStorage = new InMemoryStorage();
await newStorage.hydrate(state);
const newMemory = new Memory(newStorage);

See examples/memory-usage.ts for more comprehensive examples.

Architecture

Design Principles

  • Optional: Memory is completely optional - SDK works exactly as before if not configured
  • Non-breaking: No changes to existing API behavior
  • Modular: Easy to add new storage backends (file, PostgreSQL, Redis, hosted APIs, etc.)
  • Type-safe: Full TypeScript support with strict type checking
  • Provider-agnostic: Core features work with any storage implementation
  • Simple configuration: Minimal config with sensible defaults
  • Clear errors: Descriptive error messages when features aren't supported
  • Speakeasy-compatible: Works with generated code using #region markers

Files Added

  • src/lib/memory/types.ts - Type definitions
  • src/lib/memory/memory.ts - Main Memory class
  • src/lib/memory/storage/interface.ts - Storage interface
  • src/lib/memory/storage/in-memory.ts - In-memory storage implementation
  • src/lib/memory/index.ts - Public exports
  • tests/e2e/memory.test.ts - E2E integration tests (17 tests)
  • tests/unit/memory/memory.test.ts - Memory class unit tests (62 tests, 100% coverage)
  • tests/unit/memory/storage/in-memory.test.ts - InMemoryStorage unit tests (91 tests, 100% coverage)
  • examples/memory-usage.ts - Usage examples

Files Modified

  • src/index.ts - Export memory types and classes
  • src/sdk/sdk.ts - Add memory getter
  • src/funcs/getResponse.ts - Handle threadId/resourceId and pass to ResponseWrapper
  • src/lib/response-wrapper.ts - Implement auto-inject and auto-save

Testing

All 170 tests passing with 100% coverage on core classes

  • 91 unit tests for InMemoryStorage (100% coverage)
  • 62 unit tests for Memory class (100% coverage)
  • 17 E2E integration tests for full system
# Run all memory tests
npx vitest tests/unit/memory/ tests/e2e/memory.test.ts --run

# Run with coverage
npx vitest tests/unit/memory/ --coverage

Test Coverage:

  • Thread operations (creation, retrieval, deletion, sorting)
  • Message operations (save, retrieve, update, delete, pagination)
  • Resource management
  • Working memory (thread and resource scoped)
  • Serialization and hydration (full state and per-thread)
  • Token counting and budget-based retrieval
  • Cache management (enable, retrieve, invalidate)
  • Status-based filtering
  • Importance-based filtering
  • Error handling for unsupported features
  • Edge cases (empty arrays, null values, tokenCount:0, etc.)

Future Enhancements

Potential future additions (not in this PR):

  • File-based storage adapter
  • PostgreSQL/MySQL storage adapter
  • Redis storage adapter
  • Claude Context API storage adapter
  • OpenAI Conversations API storage adapter
  • Semantic search (vector embeddings)
  • Thread title auto-generation

Breaking Changes

None - memory is completely optional and backward compatible.

Checklist

  • Code compiles without errors
  • All tests pass (170/170)
  • 100% coverage on Memory and InMemoryStorage classes
  • Usage examples included
  • Types exported from main index
  • Documentation in code comments
  • Non-breaking changes
  • Compatible with Speakeasy generation
  • Context-aware enhancements implemented
  • Provider compatibility documented
  • Configuration simplified and streamlined
  • Clear error messages for unsupported operations

Implements a comprehensive memory system for the OpenRouter TypeScript SDK that enables:
- Thread-based conversation management
- Resource (user) management
- Working memory (both thread and resource scoped)
- Auto-injection of conversation history
- Auto-saving of messages
- Serialization/hydration for persistence
- In-memory storage implementation (with interface for future storage backends)

## Key Features

### Core Components
- **Memory**: Main API class for managing threads, resources, and working memory
- **MemoryStorage**: Interface for storage implementations
- **InMemoryStorage**: Default in-memory storage implementation
- **Types**: Comprehensive type definitions for all memory entities

### Auto-Features
- **Auto-inject**: Automatically prepends conversation history to API requests
- **Auto-save**: Automatically saves messages after responses complete
- **Configurable**: Max history messages, enable/disable auto features

### Usage
```typescript
import { OpenRouter, Memory, InMemoryStorage } from "@openrouter/sdk";

const memory = new Memory(new InMemoryStorage());
const client = new OpenRouter({ apiKey, memory });

const response = client.getResponse({
  model: "meta-llama/llama-3.2-1b-instruct",
  input: [{ role: "user", content: "Hello!" }],
  threadId: "thread-123",
  resourceId: "user-456"
});
const text = await response.text; // History auto-injected, message auto-saved
```

### Testing
- Comprehensive E2E tests covering all memory features
- All tests passing
- Usage example included in examples/memory-usage.ts

### Architecture
- Non-breaking: Memory is completely optional
- Compatible with generated Speakeasy code
- Modular: Easy to add new storage backends
- Type-safe: Full TypeScript support
Serialization is a storage concern, not a memory concern. This refactoring:
- Moves serialize/hydrate methods from Memory class to MemoryStorage interface
- Implements serialize/hydrate/serializeThread/hydrateThread in InMemoryStorage
- Updates tests to use storage.serialize() instead of memory.serialize()
- Updates usage example to show proper storage serialization pattern

This makes the architecture cleaner and allows different storage backends
to implement their own serialization strategies (e.g., direct DB export,
file formats, etc.)
Adds provider-agnostic enhancements to memory system to support advanced features like token budgeting, message editing, caching, and priority-based selection.

## New Features

### Enhanced Message Metadata (Optional)
- status: 'active' | 'archived' | 'deleted' for filtering
- importance: 0-1 score for priority-based selection
- tokenCount: Provider-calculated token count
- cacheControl: Cache configuration with expiry
- version: Message versioning for edit tracking
- editedFrom: Original message ID for edit history

### Context Window Management
- ContextWindowConfig: maxTokens, strategy, retainRecentMessages
- Strategies: fifo, priority-based, token-aware
- Token budget-aware message retrieval

### New Storage Methods (Optional)
- updateMessage: Edit existing messages
- getMessageHistory: Get message version history
- getThreadTokenCount: Calculate total tokens
- getMessagesByTokenBudget: Retrieve within token limit
- getCachedMessages: Get messages with active cache
- invalidateCache: Expire cached messages
- getMessagesByStatus: Filter by status
- getMessagesByImportance: Filter by importance score

### Memory Class Enhancements
- updateMessage: Edit messages with versioning
- getMessageVersions: Get edit history
- getThreadTokenCount: Get token usage
- getMessagesWithinBudget: Token-aware retrieval
- getCachedMessages: Cache-aware retrieval
- invalidateCache: Cache management
- getMessagesByStatus: Status filtering
- getMessagesByImportance: Priority filtering

## Design Principles
- Backward compatible: All new fields/methods are optional
- Provider agnostic: No specific API dependencies
- Graceful degradation: Falls back when storage doesn't support features
- Type safe: Full TypeScript support with proper types

## Implementation
- All optional methods implemented in InMemoryStorage
- Memory class checks for method availability before calling
- New types exported: CacheControl, ContextWindowConfig
- All existing tests pass (10/10)
- Add 10 new tests covering all context-aware enhancements
- Test message editing and version history tracking
- Test token-aware message selection within budget
- Test cache management (enable, retrieve, invalidate)
- Test message filtering by status and importance
- Test graceful degradation when storage doesn't support features
- Test contextWindow config integration
- Update usage examples to demonstrate new features
- All 20 tests passing
@mattapperson mattapperson marked this pull request as draft October 30, 2025 22:34
- Remove getCachedMessages from MemoryStorage interface, InMemoryStorage, and Memory class
- Remove getMessagesByImportance from MemoryStorage interface, InMemoryStorage, and Memory class
- Remove related tests (3 tests removed, 17 tests remaining, all passing)
- Update usage examples to remove references to removed methods
- Build successful with no TypeScript errors
@mattapperson mattapperson changed the base branch from main to mattapperson/feat/getResponse October 30, 2025 22:59
Remove unnecessary configuration options and improve developer experience:

- Remove always-true config options (autoSave, autoInject, trackTokenUsage)
- Remove unused config fields (strategy, retainRecentMessages)
- Replace custom ID generation with UUID v4
- Fix deprecated .substr() to use .slice()
- Improve error messages for unsupported storage operations
- Update examples and tests

This reduces MemoryConfig from 7 fields to 2, making the API simpler
while maintaining all functionality.
- Add comprehensive unit tests for InMemoryStorage (91 tests)
- Add comprehensive unit tests for Memory class (62 tests)
- Cover all edge cases including working memory hydration and tokenCount:0
- Add @vitest/coverage-v8 dependency for coverage reporting
- Total: 153 passing tests with 100% coverage on core memory classes

Coverage results:
- memory.ts: 100% statements, 100% branch, 100% functions
- in-memory.ts: 100% statements, 100% branch, 100% functions
@socket-security
Copy link

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​vitest/​coverage-v8@​3.2.4991007299100

View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants