feat(ai): Create unified AI package with OpenAI, Anthropic, and Gemini support

- Set up @mariozechner/ai package structure following monorepo patterns
- Install OpenAI, Anthropic, and Google Gemini SDK dependencies
- Document comprehensive API investigation for all three providers
- Design minimal unified API with streaming-first architecture
- Add models.dev integration for pricing and capabilities
- Implement automatic caching strategy for all providers
- Update project documentation with package creation guide
This commit is contained in:
Mario Zechner 2025-08-17 20:18:45 +02:00
parent 2c03724862
commit f064ea0e14
14 changed files with 7437 additions and 21 deletions

View file

@ -0,0 +1,606 @@
# Analysis: Creating Unified AI Package
## Package Structure Analysis for Pi Monorepo
Based on my examination of the existing packages (`tui`, `agent`, and `pods`), here are the comprehensive patterns and conventions used in this monorepo:
### 1. Package Naming Conventions
**Scoped NPM packages with consistent naming:**
- All packages use the `@mariozechner/` scope
- Package names follow the pattern: `@mariozechner/pi-<package-name>`
- Special case: the main CLI package is simply `@mariozechner/pi` (not `pi-pods`)
**Directory structure:**
- Packages are located in `/packages/<package-name>/`
- Directory names match the suffix of the npm package name (e.g., `tui`, `agent`, `pods`)
### 2. Package.json Structure Patterns
**Common fields across all packages:**
```json
{
"name": "@mariozechner/pi-<name>",
"version": "0.5.8", // Lockstep versioning - all packages share same version
"description": "...",
"type": "module", // All packages use ES modules
"author": "Mario Zechner",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/badlogic/pi-mono.git",
"directory": "packages/<name>"
},
"engines": {
"node": ">=20.0.0" // Consistent Node.js requirement
}
}
```
**Binary packages (agent, pods):**
- Include `"bin"` field with CLI command mapping
- Examples: `"pi-agent": "dist/cli.js"` and `"pi": "dist/cli.js"`
**Library packages (tui):**
- Include `"main"` field pointing to built entry point
- Include `"types"` field for TypeScript definitions
### 3. Scripts Configuration
**Universal scripts across all packages:**
- `"clean": "rm -rf dist"` - Removes build artifacts
- `"build": "tsc -p tsconfig.build.json"` - Builds with dedicated build config
- `"check": "biome check --write ."` - Linting and formatting
- `"prepublishOnly": "npm run clean && npm run build"` - Pre-publish cleanup
**CLI-specific build scripts:**
- Add `&& chmod +x dist/cli.js` for executable permissions
- Copy additional assets (e.g., `&& cp src/models.json dist/` for pods package)
### 4. Dependencies Structure
**Dependency hierarchy follows a clear pattern:**
```
pi-tui (foundation) -> pi-agent (uses tui) -> pi (uses agent)
```
**Internal dependencies:**
- Use exact version matching for internal packages (e.g., `"^0.5.8"`)
- Agent depends on TUI: `"@mariozechner/pi-tui": "^0.5.8"`
- Pods depends on Agent: `"@mariozechner/pi-agent": "^0.5.8"`
**External dependencies:**
- Common dependencies like `chalk` are used across multiple packages
- Specialized dependencies are package-specific (e.g., `marked` for tui, `openai` for agent)
### 5. TypeScript Configuration
**Dual TypeScript configuration approach:**
**`tsconfig.build.json` (for production builds):**
```json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
**Root `tsconfig.json` (for development and type checking):**
- Contains path mappings for cross-package imports during development
- Includes all source and test files
- Uses `"noEmit": true` for type checking without building
### 6. Source Directory Structure
**Standard structure across all packages:**
```
src/
├── index.ts # Main export file
├── cli.ts # CLI entry point (if applicable)
├── <core-files>.ts # Core functionality
├── components/ # Components (for tui)
├── tools/ # Tool implementations (for agent)
├── commands/ # Command implementations (for pods)
└── renderers/ # Output renderers (for agent)
```
### 7. Export Patterns (index.ts)
**Comprehensive type and function exports:**
- Export both types and implementation classes
- Use `export type` for type-only exports
- Group exports logically with comments
- Example from tui: exports components, interfaces, and utilities
- Example from agent: exports core classes, types, and utilities
### 8. Files Configuration
**Files included in NPM packages:**
- `"files": ["dist"]` or `"files": ["dist/**/*", "README.md"]`
- All packages include built `dist/` directory
- Some include additional files like README.md or scripts
### 9. README.md Structure
**Comprehensive documentation pattern:**
- Feature overview with key capabilities
- Quick start section with code examples
- Detailed API documentation
- Installation instructions
- Development setup
- Testing information (especially for tui)
- Examples and usage patterns
### 10. Testing Structure (TUI package)
**Dedicated test directory:**
- `test/` directory with `.test.ts` files for unit tests
- Example applications (e.g., `chat-app.ts`, `file-browser.ts`)
- Custom testing infrastructure (e.g., `virtual-terminal.ts`)
- Test script: `"test": "node --test --import tsx test/*.test.ts"`
### 11. Version Management
**Lockstep versioning:**
- All packages share the same version number
- Root package.json scripts handle version bumping across all packages
- Version sync script ensures internal dependency versions match
### 12. Build Order
**Dependency-aware build order:**
- Root build script builds packages in dependency order
- `"build": "npm run build -w @mariozechner/pi-tui && npm run build -w @mariozechner/pi-agent && npm run build -w @mariozechner/pi"`
### 13. Common Configuration Files
**Shared across monorepo:**
- `biome.json` - Unified linting and formatting configuration
- `tsconfig.base.json` - Base TypeScript configuration
- `.gitignore` - Ignores `dist/`, `node_modules/`, and other build artifacts
- Husky pre-commit hooks for formatting and type checking
### 14. Keywords and Metadata
**Descriptive keywords for NPM discovery:**
- Each package includes relevant keywords (e.g., "tui", "terminal", "agent", "ai", "llm")
- Keywords help with package discoverability
This analysis shows a well-structured monorepo with consistent patterns that would make adding new packages straightforward by following these established conventions.
## Monorepo Configuration Analysis
Based on my analysis of the pi-mono monorepo configuration, here's a comprehensive guide on how to properly integrate a new package:
### 1. Root Package.json Configuration
**Workspace Configuration:**
- Uses npm workspaces with `"workspaces": ["packages/*"]`
- All packages are located under `/packages/` directory
- Private monorepo (`"private": true`) with ESM modules (`"type": "module"`)
**Build System:**
- **Sequential Build Order**: The build script explicitly defines dependency order:
```json
"build": "npm run build -w @mariozechner/pi-tui && npm run build -w @mariozechner/pi-agent && npm run build -w @mariozechner/pi"
```
- **Dependency Chain**: `pi-tui``pi-agent``pi` (pods)
- **Important**: New packages must be inserted in the correct dependency order in the build script
**Scripts Available:**
- `clean`: Cleans all package dist folders
- `build`: Sequential build respecting dependencies
- `check`: Runs Biome formatting, package checks, and TypeScript checking
- `test`: Runs tests across all packages
- Version management scripts (lockstep versioning)
- Publishing scripts with dry-run capability
### 2. Root TypeScript Configuration
**Dual Configuration System:**
- **`tsconfig.base.json`**: Base TypeScript settings for all packages
- **`tsconfig.json`**: Development configuration with path mappings for cross-package imports
- **Package `tsconfig.build.json`**: Clean build configs per package
**Path Mappings** (in `/Users/badlogic/workspaces/pi-mono/tsconfig.json`):
```json
"paths": {
"@mariozechner/pi-tui": ["./packages/tui/src/index.ts"],
"@mariozechner/pi-agent": ["./packages/agent/src/index.ts"],
"@mariozechner/pi": ["./packages/pods/src/index.ts"]
}
```
### 3. Package Dependencies and Structure
**Dependency Structure:**
- `pi-tui` (base library) - no internal dependencies
- `pi-agent` depends on `pi-tui`
- `pi` (pods) depends on `pi-agent`
**Standard Package Structure:**
```
packages/new-package/
├── src/
│ ├── index.ts # Main export file
│ └── ... # Implementation files
├── package.json # Package configuration
├── tsconfig.build.json # Build-specific TypeScript config
├── README.md # Package documentation
└── dist/ # Build output (gitignored)
```
### 4. Version Management
**Lockstep Versioning:**
- All packages share the same version number (currently 0.5.8)
- Automated version sync script: `/Users/badlogic/workspaces/pi-mono/scripts/sync-versions.js`
- Inter-package dependencies are automatically updated to match current versions
**Version Scripts:**
- `npm run version:patch/minor/major` - Updates all package versions and syncs dependencies
- Automatic dependency version synchronization
### 5. GitIgnore Patterns
**Package-Level Ignores:**
```
packages/*/node_modules/
packages/*/dist/
```
Plus standard ignores for logs, IDE files, environment files, etc.
## How to Integrate a New Package
### Step 1: Create Package Structure
```bash
mkdir packages/your-new-package
cd packages/your-new-package
```
### Step 2: Create package.json
```json
{
"name": "@mariozechner/your-new-package",
"version": "0.5.8",
"description": "Your package description",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist"],
"scripts": {
"clean": "rm -rf dist",
"build": "tsc -p tsconfig.build.json",
"check": "biome check --write .",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
// Add dependencies on other packages in the monorepo if needed
// "@mariozechner/pi-tui": "^0.5.8"
},
"devDependencies": {},
"keywords": ["relevant", "keywords"],
"author": "Mario Zechner",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/badlogic/pi-mono.git",
"directory": "packages/your-new-package"
},
"engines": {
"node": ">=20.0.0"
}
}
```
### Step 3: Create tsconfig.build.json
```json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
### Step 4: Create src/index.ts
```typescript
// Main exports for your package
export * from './your-main-module.js';
```
### Step 5: Update Root Configuration
**Add to `/Users/badlogic/workspaces/pi-mono/tsconfig.json` paths:**
```json
"paths": {
"@mariozechner/pi-tui": ["./packages/tui/src/index.ts"],
"@mariozechner/pi-agent": ["./packages/agent/src/index.ts"],
"@mariozechner/pi": ["./packages/pods/src/index.ts"],
"@mariozechner/your-new-package": ["./packages/your-new-package/src/index.ts"]
}
```
**Update build script in root `/Users/badlogic/workspaces/pi-mono/package.json`:**
```json
"build": "npm run build -w @mariozechner/pi-tui && npm run build -w @mariozechner/pi-agent && npm run build -w @mariozechner/your-new-package && npm run build -w @mariozechner/pi"
```
(Insert in correct dependency order)
### Step 6: Update sync-versions.js
If your package depends on other monorepo packages, add synchronization logic to `/Users/badlogic/workspaces/pi-mono/scripts/sync-versions.js`.
### Step 7: Install and Test
```bash
# From monorepo root
npm install
npm run build
npm run check
```
## Key Requirements for New Packages
1. **Must use ESM modules** (`"type": "module"`)
2. **Must follow lockstep versioning** (same version as other packages)
3. **Must be placed in correct build order** based on dependencies
4. **Must use tab indentation** (Biome config: `"indentStyle": "tab"`)
5. **Must avoid `any` types** unless absolutely necessary (project instruction)
6. **Must include proper TypeScript declarations** (`"declaration": true`)
7. **Must use Node.js >= 20.0.0** (engine requirement)
8. **Must follow the standard package structure** with src/, dist/, proper exports
## Development Workflow
1. **Development**: Use `tsx` to run source files directly (no build needed)
2. **Type Checking**: `npm run check` works across all packages
3. **Building**: Sequential builds respect dependency order
4. **Publishing**: Automatic version sync and cross-package dependency updates
5. **Testing**: Each package can have its own test suite
This monorepo is well-structured for maintaining multiple related packages with clean dependency management and automated version synchronization.
## Detailed Findings: Unified AI API Requirements Based on Current pi-agent Usage
After thoroughly analyzing the existing agent package (`/Users/badlogic/workspaces/pi-mono/packages/agent`), here are the comprehensive requirements for a unified AI API based on current usage patterns:
### **1. Core API Structure & Event System**
**Current Pattern:**
- Event-driven architecture using `AgentEvent` types
- Single `AgentEventReceiver` interface for all output handling
- Support for both single-shot and interactive modes
**Required API Features:**
```typescript
type AgentEvent =
| { type: "session_start"; sessionId: string; model: string; api: string; baseURL: string; systemPrompt: string }
| { type: "assistant_start" }
| { type: "reasoning"; text: string }
| { type: "tool_call"; toolCallId: string; name: string; args: string }
| { type: "tool_result"; toolCallId: string; result: string; isError: boolean }
| { type: "assistant_message"; text: string }
| { type: "error"; message: string }
| { type: "user_message"; text: string }
| { type: "interrupted" }
| { type: "token_usage"; inputTokens: number; outputTokens: number; totalTokens: number; cacheReadTokens: number; cacheWriteTokens: number; reasoningTokens: number }
```
### **2. OpenAI API Integration Patterns**
**Current Implementation:**
- Uses OpenAI SDK v5.12.2 (`import OpenAI from "openai"`)
- Supports both Chat Completions (`/v1/chat/completions`) and Responses API (`/v1/responses`)
- Provider detection based on base URL patterns
**Provider Support Required:**
```typescript
// Detected providers based on baseURL patterns
type Provider = "openai" | "gemini" | "groq" | "anthropic" | "openrouter" | "other"
// Provider-specific configurations
interface ProviderConfig {
openai: { reasoning_effort: "minimal" | "low" | "medium" | "high" }
gemini: { extra_body: { google: { thinking_config: { thinking_budget: number, include_thoughts: boolean } } } }
groq: { reasoning_format: "parsed", reasoning_effort: string }
openrouter: { reasoning: { effort: "low" | "medium" | "high" } }
}
```
### **3. Streaming vs Non-Streaming**
**Current Status:**
- **No streaming currently implemented** - uses standard request/response
- All API calls are non-streaming: `await client.chat.completions.create()` and `await client.responses.create()`
- Events are emitted synchronously after full response
**Streaming Requirements for Unified API:**
- Support for streaming responses with partial content updates
- Event-driven streaming with `assistant_message_delta` events
- Proper handling of tool call streaming
- Reasoning token streaming for supported models
### **4. Tool Calling Architecture**
**Current Implementation:**
```typescript
// Tool definitions for both APIs
toolsForResponses: Array<{type: "function", name: string, description: string, parameters: object}>
toolsForChat: ChatCompletionTool[]
// Tool execution with abort support
async function executeTool(name: string, args: string, signal?: AbortSignal): Promise<string>
// Built-in tools: read, list, bash, glob, rg (ripgrep)
```
**Unified API Requirements:**
- Automatic tool format conversion between Chat Completions and Responses API
- Built-in tools with filesystem and shell access
- Custom tool registration capability
- Tool execution with proper abort/interrupt handling
- Tool result streaming for long-running operations
### **5. Message Structure Handling**
**Current Pattern:**
- Dual message format support based on API type
- Automatic conversion between formats in `setEvents()` method
**Chat Completions Format:**
```typescript
{ role: "system" | "user" | "assistant" | "tool", content: string, tool_calls?: any[] }
```
**Responses API Format:**
```typescript
{ type: "message" | "function_call" | "function_call_output", content: any[] }
```
### **6. Session Persistence System**
**Current Implementation:**
```typescript
interface SessionData {
config: AgentConfig
events: SessionEvent[]
totalUsage: TokenUsage
}
// File-based persistence in ~/.pi/sessions/
// JSONL format with session headers and event entries
// Automatic session continuation support
```
**Requirements:**
- Directory-based session organization
- Event replay capability for session restoration
- Cumulative token usage tracking
- Session metadata (config, timestamps, working directory)
### **7. Token Counting & Usage Tracking**
**Current Implementation:**
```typescript
interface TokenUsage {
inputTokens: number
outputTokens: number
totalTokens: number
cacheReadTokens: number
cacheWriteTokens: number
reasoningTokens: number // For o1/o3 and reasoning models
}
```
**Provider-Specific Token Mapping:**
- OpenAI: `prompt_tokens`, `completion_tokens`, `cached_tokens`, `reasoning_tokens`
- Responses API: `input_tokens`, `output_tokens`, `cached_tokens`, `reasoning_tokens`
- Cumulative tracking across conversations
### **8. Abort/Interrupt Handling**
**Current Pattern:**
```typescript
class Agent {
private abortController: AbortController | null = null
async ask(message: string) {
this.abortController = new AbortController()
// Pass signal to all API calls and tool executions
}
interrupt(): void {
this.abortController?.abort()
}
}
```
**Requirements:**
- AbortController integration for all async operations
- Graceful interruption of API calls, tool execution, and streaming
- Proper cleanup and "interrupted" event emission
- Signal propagation to nested operations
### **9. Reasoning/Thinking Support**
**Current Implementation:**
```typescript
// Provider-specific reasoning extraction
function parseReasoningFromMessage(message: any, baseURL?: string): {
cleanContent: string
reasoningTexts: string[]
}
// Automatic reasoning support detection
async function checkReasoningSupport(client, model, api, baseURL, signal): Promise<boolean>
```
**Provider Support:**
- **OpenAI o1/o3**: Full thinking content via Responses API
- **Groq GPT-OSS**: Reasoning via `reasoning_format: "parsed"`
- **Gemini 2.5**: Thinking content via `<thought>` tags
- **OpenRouter**: Model-dependent reasoning support
### **10. Error Handling Patterns**
**Current Approach:**
- Try/catch blocks around all API calls
- Error events emitted through event system
- Specific error handling for reasoning model failures
- Provider-specific error interpretation
### **11. Configuration Management**
**Current Structure:**
```typescript
interface AgentConfig {
apiKey: string
baseURL: string
model: string
api: "completions" | "responses"
systemPrompt: string
}
```
**Provider Detection:**
```typescript
function detectProvider(baseURL?: string): Provider {
// URL pattern matching for automatic provider configuration
}
```
### **12. Output Rendering System**
**Current Renderers:**
- **ConsoleRenderer**: Terminal output with animations, token display
- **TuiRenderer**: Full interactive TUI with pi-tui integration
- **JsonRenderer**: JSONL event stream output
**Requirements:**
- Event-based rendering architecture
- Real-time token usage display
- Loading animations for async operations
- Markdown rendering support
- Tool execution progress indication
### **Summary: Key Unified API Requirements**
1. **Event-driven architecture** with standardized event types
2. **Dual API support** (Chat Completions + Responses API) with automatic format conversion
3. **Provider abstraction** with automatic detection and configuration
4. **Comprehensive tool system** with abort support and built-in tools
5. **Session persistence** with event replay and token tracking
6. **Reasoning/thinking support** across multiple providers
7. **Interrupt handling** with AbortController integration
8. **Token usage tracking** with provider-specific mapping
9. **Flexible rendering** through event receiver pattern
10. **Configuration management** with provider-specific settings
The unified API should maintain this event-driven, provider-agnostic approach while adding streaming capabilities and enhanced tool execution features that the current implementation lacks.

View file

@ -0,0 +1,46 @@
# Create AI Package with Unified API
**Status:** Done
**Agent PID:** 10965
## Original Todo
ai: create a new package ai (package name @mariozechner/ai) which implements a common api for the openai, anthropic, and google gemini apis
- look at the other packages and how they are set up, mirror that setup for ai
- install the latest version of each dependency via npm in the ai package
- openai@5.12.2
- @anthropic-ai/sdk@0.60.0
- @google/genai@1.14.0
- investigate the APIs in their respective node_modules folder so you understand how to use them. specifically, we need to understand how to:
- stream responses, including reasoning/thinking tokens and tool calls
- abort requests
- handle errors
- handle stop reasons
- maintain the context (message history) such that it can be serialized in a uniform format to disk, and deserialized again later and used with the other api
- count tokens (input, output, cached read, cached write)
- enable caching
- Create a plan.md in the ai package that details how the unified API on top of all three could look like. we want the most minimal api possible, which allows serialization/deserialization, turning on/off reasoning/thinking, and handle system prompt and tool specifications
## Description
Create the initial package scaffold for @mariozechner/ai following the established monorepo patterns, install the required dependencies (openai, anthropic, google genai SDKs), and create a plan.md file that details the unified API design for all three providers.
*Read [analysis.md](./analysis.md) in full for detailed codebase research and context*
## Implementation Plan
- [x] Create package directory structure at packages/ai/
- [x] Create package.json with proper configuration following monorepo patterns
- [x] Create tsconfig.build.json for build configuration
- [x] Create initial src/index.ts file
- [x] Add package to root tsconfig.json path mappings
- [x] Update root package.json build script to include ai package
- [x] Install dependencies: openai@5.12.2, @anthropic-ai/sdk@0.60.0, @google/genai@1.14.0
- [x] Create README.md with package description
- [x] Create plan.md detailing the unified API design
- [x] Investigate OpenAI, Anthropic, and Gemini APIs in detail
- [x] Document implementation details for each API
- [x] Update todos/project-description.md with "How to Create a New Package" section
- [x] Update todos/project-description.md Testing section to reflect that tui has Node.js built-in tests
- [x] Run npm install from root to link everything
- [x] Verify package builds correctly with npm run build
## Notes
[Implementation notes]

View file

@ -39,4 +39,98 @@ A comprehensive toolkit for managing Large Language Model (LLM) deployments and
- Publish: `npm run publish`
## Testing
Currently no formal testing framework is configured. Test infrastructure exists but no actual test files or framework dependencies are present.
The TUI package includes comprehensive tests using Node.js built-in test framework:
- Unit tests in `packages/tui/test/*.test.ts`
- Test runner: `node --test --import tsx test/*.test.ts`
- Virtual terminal for TUI testing via `@xterm/headless`
- Example applications for manual testing
## How to Create a New Package
Follow these steps to add a new package to the monorepo:
1. **Create package directory structure:**
```bash
mkdir -p packages/your-package/src
```
2. **Create package.json:**
```json
{
"name": "@mariozechner/your-package",
"version": "0.5.8",
"description": "Package description",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist", "README.md"],
"scripts": {
"clean": "rm -rf dist",
"build": "tsc -p tsconfig.build.json",
"check": "biome check --write .",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {},
"devDependencies": {},
"keywords": ["relevant", "keywords"],
"author": "Mario Zechner",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/badlogic/pi-mono.git",
"directory": "packages/your-package"
},
"engines": {
"node": ">=20.0.0"
}
}
```
3. **Create tsconfig.build.json:**
```json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
4. **Create src/index.ts:**
```typescript
// Main exports for your package
export const version = "0.5.8";
```
5. **Update root tsconfig.json paths:**
Add your package to the `paths` mapping in the correct dependency order:
```json
"paths": {
"@mariozechner/pi-tui": ["./packages/tui/src/index.ts"],
"@mariozechner/your-package": ["./packages/your-package/src/index.ts"],
// ... other packages
}
```
6. **Update root package.json build script:**
Insert your package in the correct dependency order:
```json
"build": "npm run build -w @mariozechner/pi-tui && npm run build -w @mariozechner/your-package && ..."
```
7. **Install and verify:**
```bash
npm install
npm run build
npm run check
```
**Important Notes:**
- All packages use lockstep versioning (same version number)
- Follow dependency order: foundational packages build first
- Use ESM modules (`"type": "module"`)
- No `any` types unless absolutely necessary
- Include README.md with package documentation