diff --git a/FRAMEWORK.md b/FRAMEWORK.md new file mode 100644 index 0000000..1274527 --- /dev/null +++ b/FRAMEWORK.md @@ -0,0 +1,303 @@ +# Studio.ai Framework + +This document describes the Studio.ai framework - a custom-built AI agent framework for intelligent coding assistance. + +## Overview + +Studio.ai is a lightweight, modern framework that provides: + +- Agent orchestration and tool execution +- Memory system with SQLite storage +- Logging and monitoring +- Integration with AI SDK (OpenAI) +- Polished, professional UI/UX + +## Architecture + +### Core Components + +#### 1. Studio.ai Framework (`src/studio/`) + +The Studio.ai framework consists of several modules: + +**`studio.ts`** - Main orchestrator +- Manages multiple agents +- Provides invoke/stream methods +- Integrates storage and logging + +**`agent.ts`** - Agent implementation +- Executes AI model with tools +- Manages instructions and configuration +- Supports both invoke and stream modes +- Integrates with memory system + +**`tools.ts`** - Tool creation utilities +- Defines tool interface +- `createTool` function for tool definitions +- Uses Zod for schema validation + +**`memory.ts`** - Memory system +- Stores conversation history +- Thread management +- Supports semantic recall (placeholder) +- Storage adapter pattern + +**`libsql.ts`** - SQLite storage +- Implements `StorageAdapter` interface +- Uses `better-sqlite3` for database access +- Stores messages by thread +- Vector storage placeholder for future enhancements + +**`logger.ts`** - Logging system +- Simple console-based logger +- Supports debug, info, warn, error levels +- Pino-compatible interface + +**`fastembed.ts`** - Embedding utilities +- Placeholder for embedding functionality +- Can be extended with real embedding models + +**`index.ts`** - Framework exports +- Central export point for all framework components + +#### 2. Server (`src/server.ts`) + +Custom HTTP server that: +- Serves the Studio.ai UI from `public/` directory +- Provides REST API endpoints for agent invocation +- Supports both invoke and stream modes +- Handles CORS for local development + +### UI/UX Design + +Studio.ai features a modern Voice UI-inspired interface with: + +**Visual Design** +- Pure black background (#000000) for deep contrast +- White text with grayscale hierarchy +- Subtle accent colors: Green (#00ff88), Blue (#0088ff), Orange (#ff8800) +- Minimalist aesthetic with focus on content +- Clean, professional appearance + +**User Experience** +- Intuitive three-panel layout +- Real-time status indicators +- File attachment support +- Responsive design +- Accessible components + +### API Endpoints + +**GET /** - Serves the UI + +**POST /api/agents/{agentName}/invoke** - Invoke agent (non-streaming) +```json +{ + "messages": [ + { "role": "user", "content": "Hello" } + ], + "threadId": "optional-thread-id" +} +``` + +**POST /api/agents/{agentName}/stream** - Stream agent response +Same payload as invoke, returns Server-Sent Events (SSE) + +### Differences from Mastra + +1. **Modern UI/UX**: Polished interface with visual effects and smooth animations +2. **Simplified Architecture**: No separate CLI, just a Node.js server +3. **Direct Dependencies**: Uses AI SDK directly instead of abstraction layers +4. **Minimal Features**: Only implements features actually needed +5. **Custom Branding**: Studio.ai identity throughout +6. **TypeScript**: Full TypeScript support with proper type safety +7. **No External Services**: Everything runs locally except AI model API calls + +## Dependencies + +### Removed +- `@mastra/core` +- `@mastra/memory` +- `@mastra/libsql` +- `@mastra/loggers` +- `@mastra/mcp` +- `@mastra/fastembed` +- `mastra` (CLI) + +### Added +- `ai` - Vercel AI SDK +- `better-sqlite3` - SQLite database +- `tsx` - TypeScript execution for development + +### Retained +- `@ai-sdk/openai` - OpenAI integration +- `@e2b/code-interpreter` - E2B sandbox integration +- `zod` - Schema validation + +## Running the Project + +### Development +```bash +npm run dev +``` + +This starts the server with hot reload using `tsx`. + +### Production Build +```bash +npm run build +npm start +``` + +This compiles TypeScript to JavaScript and runs the compiled server. + +## Configuration + +The project uses environment variables for configuration: +- `OPENAI_API_KEY` - OpenAI API key +- `E2B_API_KEY` - E2B sandbox API key +- `PORT` - Server port (default: 8787) + +## Future Enhancements + +Potential improvements to the custom framework: + +1. **Real Embeddings**: Replace placeholder embedding function with actual model +2. **Vector Search**: Implement real vector similarity search in LibSQLVector +3. **Advanced Memory**: Add semantic recall and context windowing +4. **Additional Storage**: Support other databases (PostgreSQL, MongoDB, etc.) +5. **Monitoring**: Add request tracing and performance metrics +6. **Testing**: Add unit and integration tests +7. **Authentication**: Add API key or OAuth support +8. **Rate Limiting**: Protect endpoints from abuse + +## Migration Notes + +When migrating from Mastra: + +1. Import paths changed from `@mastra/*` to `../studio/*` +2. Tool creation unchanged - same `createTool` interface +3. Agent configuration unchanged - same `Agent` class interface +4. Memory API slightly different but compatible +5. No CLI commands - use npm scripts instead +6. New modern UI with Studio.ai branding + +## Testing + +To test the implementation: + +1. Start the server: `npm run dev` +2. Open browser to `http://localhost:8787` +3. Enter a test message in the UI +4. Verify agent responds correctly + +For API testing: +```bash +curl -X POST http://localhost:8787/api/agents/codingAgent/invoke \ + -H "Content-Type: application/json" \ + -d '{"messages": [{"role": "user", "content": "Hello"}]}' +``` + +## Integrations + +Studio.ai supports extensible integrations to enhance agent capabilities: + +### Current Integrations + +**E2B Code Sandbox** +- Secure code execution environment +- Multi-language support (Python, JavaScript, TypeScript) +- File system operations +- Package management +- Real-time output streaming + +**OpenAI API** +- GPT-4 model support via AI SDK +- Streaming responses +- Tool calling capabilities +- Context management + +**SQLite Storage** +- Persistent conversation history +- Thread-based organization +- Vector storage support (placeholder for embeddings) +- Efficient local storage + +### Extensible Integration Framework + +The tool system allows easy addition of new integrations: + +```typescript +import { createTool } from '../studio/tools'; +import z from 'zod'; + +export const myIntegration = createTool({ + id: 'myIntegration', + description: 'Description of integration', + inputSchema: z.object({ + param: z.string().describe('Parameter description'), + }), + outputSchema: z.object({ + result: z.string().describe('Result description'), + }), + execute: async ({ context }) => { + // Integration logic here + return { result: 'Success' }; + }, +}); +``` + +### Potential Integrations + +Studio.ai can be extended with additional integrations: + +**Development Tools** +- GitHub API - Repository management, PR creation, issue tracking +- GitLab API - Similar GitHub functionality for GitLab users +- Jira API - Task management and sprint planning +- Linear API - Modern issue tracking integration + +**Cloud Platforms** +- AWS SDK - Cloud resource management +- Google Cloud SDK - GCP service integration +- Azure SDK - Microsoft cloud services +- Vercel API - Deployment automation + +**Data & AI** +- Pinecone - Vector database for semantic search +- Supabase - Backend-as-a-service integration +- Anthropic Claude - Alternative LLM provider +- Hugging Face - Open-source model integration + +**Communication** +- Slack API - Team notifications and bot integration +- Discord API - Community bot functionality +- Email APIs - SendGrid, Mailgun for notifications +- Twilio - SMS and voice capabilities + +**Databases** +- PostgreSQL - SQL database operations +- MongoDB - NoSQL database integration +- Redis - Caching and pub/sub +- Prisma - Type-safe database toolkit + +**File Storage** +- S3-compatible - Object storage integration +- Google Drive - Cloud file management +- Dropbox - File synchronization +- Cloudflare R2 - Edge storage + +### Integration Best Practices + +When adding new integrations: + +1. **Security**: Store API keys in environment variables +2. **Error Handling**: Implement comprehensive error catching +3. **Rate Limiting**: Respect API rate limits +4. **Documentation**: Document tool usage in agent instructions +5. **Testing**: Validate integration functionality +6. **Type Safety**: Use Zod schemas for input/output validation + +## License + +Studio.ai is part of the template-coding-agent project and follows the Apache-2.0 license. diff --git a/README.md b/README.md index a66e8c0..7518d16 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# E2B Code Execution Agent +# Studio.ai - AI Coding Agent -An advanced Mastra template that provides a coding agent capable of planning, writing, executing, and iterating on code in secure, isolated E2B sandboxes with comprehensive file management and development workflow capabilities. +Studio.ai is an advanced AI coding agent framework with secure E2B sandbox execution, comprehensive file management, and multi-language support for Python, JavaScript, and TypeScript development workflows. ## Overview -This template demonstrates how to build an AI coding assistant that can work with real development environments. The agent can create sandboxes, manage files and directories, execute code in multiple languages, and monitor development workflows - all within secure, isolated E2B environments. +Studio.ai demonstrates how to build an intelligent AI coding assistant that works with real development environments. The agent can create sandboxes, manage files and directories, execute code in multiple languages, and monitor development workflows - all within secure, isolated E2B environments. + +This project features a custom-built agent framework with a polished, modern UI/UX designed for professional developers. ## Features @@ -27,9 +29,9 @@ This template demonstrates how to build an AI coding assistant that can work wit 1. **Clone and install dependencies:** ```bash - git clone https://github.com/mastra-ai/template-coding-agent.git + git clone https://github.com/astickleyid/template-coding-agent.git cd template-coding-agent - pnpm install + npm install ``` 2. **Set up environment variables:** @@ -47,11 +49,25 @@ This template demonstrates how to build an AI coding assistant that can work wit 3. **Start the development server:** ```bash - pnpm run dev + npm run dev ``` +4. **Open the bespoke coding console (optional):** + + A fully client-side playground for the coding agent lives in `public/index.html`. Navigate to `http://localhost:8787` in your browser to access the console. The server serves both the API and the UI. + ## Architecture +### Studio.ai Framework + +The custom-built Studio.ai framework provides a complete agent orchestration system located in `src/studio/`: + +- **Agent System**: AI agent with tool execution and streaming support +- **Memory System**: Conversation history with SQLite storage +- **Tool System**: Type-safe tool definitions with Zod validation +- **Storage**: SQLite-based persistent storage +- **Logger**: Console-based logging system + ### Core Components #### **Coding Agent** (`src/mastra/agents/coding-agent.ts`) @@ -168,14 +184,28 @@ export const codingAgent = new Agent({ ### Project Structure ```text -src/mastra/ - agents/ - coding-agent.ts # Main coding agent with development capabilities - tools/ - e2b.ts # Complete E2B sandbox interaction toolkit - index.ts # Mastra configuration with storage and logging +src/ + studio/ # Studio.ai framework + studio.ts # Main framework orchestrator + agent.ts # Agent implementation with AI SDK + tools.ts # Tool creation utilities + memory.ts # Memory system + libsql.ts # SQLite storage + logger.ts # Logger implementation + fastembed.ts # Embedding utilities + mastra/ + agents/ + coding-agent.ts # Main coding agent + tools/ + e2b.ts # E2B sandbox toolkit + index.ts # Agent configuration + server.ts # HTTP server for API and UI +public/ + index.html # Studio.ai UI + styles.css # Modern, polished styling + app.js # Client-side application logic ``` ## License -This project is part of the Mastra ecosystem and follows the same licensing terms. +This project is licensed under the Apache-2.0 License. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..28fde5d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1404 @@ +{ + "name": "studio-ai", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "studio-ai", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/openai": "^1.3.24", + "@e2b/code-interpreter": "^1.5.1", + "ai": "^4.0.0", + "better-sqlite3": "^11.0.0", + "zod": "^3.25.76" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.11", + "@types/node": "^24.1.0", + "tsx": "^4.19.0", + "typescript": "^5.8.3" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/@ai-sdk/openai": { + "version": "1.3.24", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.24.tgz", + "integrity": "sha512-GYXnGJTHRTZc4gJMSmFRgEQudjqd4PUN0ZjQhPwOAYH1yOAvQoG/Ikqs+HyISRbLPCrhbZnPKCNHuRU4OfpW0Q==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", + "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", + "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@ai-sdk/react": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", + "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/ui-utils": "1.2.11", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/ui-utils": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", + "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.9.0.tgz", + "integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@connectrpc/connect": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.0.0-rc.3.tgz", + "integrity": "sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.2.0" + } + }, + "node_modules/@connectrpc/connect-web": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.0.0-rc.3.tgz", + "integrity": "sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.2.0", + "@connectrpc/connect": "2.0.0-rc.3" + } + }, + "node_modules/@e2b/code-interpreter": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@e2b/code-interpreter/-/code-interpreter-1.5.1.tgz", + "integrity": "sha512-mkyKjAW2KN5Yt0R1I+1lbH3lo+W/g/1+C2lnwlitXk5wqi/g94SEO41XKdmDf5WWpKG3mnxWDR5d6S/lyjmMEw==", + "license": "MIT", + "dependencies": { + "e2b": "^1.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.0.tgz", + "integrity": "sha512-5x08bUtU8hfboMTrJ7mEO4CpepS9yBwAqcL52y86SWNmbPX8LVbNs3EP4cNrIZgdjk2NAlP2ahNihozpoZIxSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.14.0" + } + }, + "node_modules/ai": { + "version": "4.3.19", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz", + "integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/react": "1.2.12", + "@ai-sdk/ui-utils": "1.2.11", + "@opentelemetry/api": "1.9.0", + "jsondiffpatch": "0.6.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, + "node_modules/e2b": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/e2b/-/e2b-1.13.2.tgz", + "integrity": "sha512-m8acE/MzMAJo1A57DakR2X1Sl5Mt1tcQO2aJfygNaQHLXby/4xsjF0UeJUB70jF7xntiR41pAMbZEHnkzrT9tw==", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.6.2", + "@connectrpc/connect": "2.0.0-rc.3", + "@connectrpc/connect-web": "2.0.0-rc.3", + "compare-versions": "^6.1.0", + "openapi-fetch": "^0.9.7", + "platform": "^1.3.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz", + "integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "license": "MIT", + "dependencies": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.78.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz", + "integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi-fetch": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.8.tgz", + "integrity": "sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg==", + "license": "MIT", + "dependencies": { + "openapi-typescript-helpers": "^0.0.8" + } + }, + "node_modules/openapi-typescript-helpers": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz", + "integrity": "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g==", + "license": "MIT" + }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/swr": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/package.json b/package.json index 542f309..04bef3c 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { - "name": "coding-agent", - "version": "0.2.0", - "description": "Advanced Mastra AI coding agent with secure E2B sandbox execution, comprehensive file management, and multi-language support for Python, JavaScript, and TypeScript development workflows", + "name": "studio-ai", + "version": "1.0.0", + "description": "Studio.ai - Advanced AI coding agent framework with secure E2B sandbox execution, comprehensive file management, and multi-language support for Python, JavaScript, and TypeScript development workflows", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "mastra dev", - "build": "mastra build", - "start": "mastra start" + "dev": "tsx src/server.ts", + "build": "tsc", + "start": "node dist/server.js" }, "keywords": [], "author": "", @@ -19,17 +19,14 @@ "dependencies": { "@ai-sdk/openai": "^1.3.24", "@e2b/code-interpreter": "^1.5.1", - "@mastra/core": "latest", - "@mastra/fastembed": "latest", - "@mastra/libsql": "latest", - "@mastra/loggers": "latest", - "@mastra/mcp": "latest", - "@mastra/memory": "latest", + "ai": "^4.0.0", + "better-sqlite3": "^11.0.0", "zod": "^3.25.76" }, "devDependencies": { + "@types/better-sqlite3": "^7.6.11", "@types/node": "^24.1.0", - "mastra": "latest", + "tsx": "^4.19.0", "typescript": "^5.8.3" } } diff --git a/public/app.js b/public/app.js new file mode 100644 index 0000000..bd61521 --- /dev/null +++ b/public/app.js @@ -0,0 +1,681 @@ +const STORAGE_KEY = 'studio-ai-console-settings'; +const defaultSettings = { + baseUrl: 'http://localhost:8787/api', + agentId: 'codingAgent', + endpoint: 'invoke', + stream: false, + showRaw: false, + systemPrompt: '', + metadata: '', + threadId: '', + toolTarget: 'auto', +}; + +const settings = loadSettings(); +const state = { + messages: [], + attachments: [], + isSending: false, +}; + +function generateThreadId() { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + return `thread_${Math.random().toString(36).slice(2, 10)}`; +} + +const ui = { + transcript: document.getElementById('transcript'), + status: document.getElementById('status-indicator'), + baseUrl: document.getElementById('base-url'), + agentId: document.getElementById('agent-id'), + endpoint: document.getElementById('endpoint'), + messageInput: document.getElementById('message-input'), + sendBtn: document.getElementById('send-btn'), + newThread: document.getElementById('new-thread-btn'), + threadId: document.getElementById('thread-id-input'), + copyThreadId: document.getElementById('copy-thread-id'), + systemPrompt: document.getElementById('system-prompt'), + metadata: document.getElementById('metadata-editor'), + streamToggle: document.getElementById('stream-toggle'), + showRawToggle: document.getElementById('show-raw-toggle'), + rawPreview: document.getElementById('raw-preview'), + payloadView: document.getElementById('payload-view'), + responseView: document.getElementById('response-view'), + toolEvents: document.getElementById('tool-events'), + copyPayload: document.getElementById('copy-payload'), + attachmentInput: document.getElementById('attachment-input'), + attachmentList: document.getElementById('attachment-list'), + toolTarget: document.getElementById('tool-target'), + clearComposer: document.getElementById('clear-composer'), +}; + +initialize(); + +function initialize() { + if (!settings.threadId) { + updateSetting('threadId', generateThreadId()); + } + + ui.baseUrl.value = settings.baseUrl; + ui.agentId.value = settings.agentId; + ui.endpoint.value = settings.endpoint; + ui.streamToggle.checked = settings.stream; + ui.showRawToggle.checked = settings.showRaw; + ui.systemPrompt.value = settings.systemPrompt; + ui.metadata.value = settings.metadata; + ui.threadId.value = settings.threadId; + ui.toolTarget.value = settings.toolTarget; + ui.rawPreview.hidden = !settings.showRaw; + + ui.baseUrl.addEventListener('change', event => updateSetting('baseUrl', event.target.value.trim())); + ui.agentId.addEventListener('change', event => updateSetting('agentId', event.target.value.trim())); + ui.endpoint.addEventListener('change', event => updateSetting('endpoint', event.target.value)); + ui.streamToggle.addEventListener('change', event => updateSetting('stream', event.target.checked)); + ui.showRawToggle.addEventListener('change', event => { + updateSetting('showRaw', event.target.checked); + ui.rawPreview.hidden = !event.target.checked; + updatePayloadPreview(); + }); + ui.systemPrompt.addEventListener('input', event => { + updateSetting('systemPrompt', event.target.value); + updatePayloadPreview(); + }); + ui.metadata.addEventListener('input', event => { + updateSetting('metadata', event.target.value); + updatePayloadPreview(); + }); + ui.threadId.addEventListener('change', event => { + updateSetting('threadId', event.target.value.trim()); + updatePayloadPreview(); + }); + ui.toolTarget.addEventListener('change', event => { + updateSetting('toolTarget', event.target.value); + updatePayloadPreview(); + }); + ui.copyThreadId.addEventListener('click', handleCopyThreadId); + ui.copyPayload.addEventListener('click', handleCopyPayload); + ui.sendBtn.addEventListener('click', handleSendMessage); + ui.newThread.addEventListener('click', resetConversation); + ui.clearComposer.addEventListener('click', () => { + ui.messageInput.value = ''; + clearAttachments(); + updatePayloadPreview(); + }); + ui.messageInput.addEventListener('input', updatePayloadPreview); + ui.attachmentInput.addEventListener('change', handleAttachmentSelection); + + renderTranscript(); + updatePayloadPreview(); + updateInspector(); +} + +function loadSettings() { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (!stored) return { ...defaultSettings }; + const parsed = JSON.parse(stored); + return { ...defaultSettings, ...parsed }; + } catch (error) { + console.warn('Unable to load settings from storage', error); + return { ...defaultSettings }; + } +} + +function persistSettings() { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); + } catch (error) { + console.warn('Unable to persist settings', error); + } +} + +function updateSetting(key, value) { + settings[key] = value; + persistSettings(); +} + +function resetConversation() { + state.messages = []; + updateSetting('threadId', generateThreadId()); + ui.threadId.value = settings.threadId; + clearAttachments(); + renderTranscript(); + updatePayloadPreview(); + updateInspector(); +} + +function renderTranscript() { + ui.transcript.innerHTML = ''; + const template = document.getElementById('message-template'); + state.messages.forEach(message => { + const clone = template.content.firstElementChild.cloneNode(true); + clone.classList.add(message.role); + if (message.internal) { + clone.classList.add('internal'); + } + clone.querySelector('.role').textContent = formatRoleLabel(message.role); + clone.querySelector('.timestamp').textContent = message.timestamp + ? new Date(message.timestamp).toLocaleTimeString() + : new Date().toLocaleTimeString(); + const contentNode = clone.querySelector('.content'); + contentNode.textContent = message.content || ''; + + if (message.attachments?.length) { + const attachmentsList = document.createElement('div'); + attachmentsList.className = 'attachment-list'; + message.attachments.forEach(attachment => { + const badge = document.createElement('span'); + badge.className = 'badge'; + badge.textContent = `${attachment.name} (${formatBytes(attachment.size)})`; + attachmentsList.appendChild(badge); + }); + contentNode.appendChild(document.createElement('hr')); + contentNode.appendChild(attachmentsList); + } + + ui.transcript.appendChild(clone); + }); + ui.transcript.scrollTop = ui.transcript.scrollHeight; +} + +function formatRoleLabel(role) { + if (!role) return 'Message'; + switch (role) { + case 'user': + return 'You'; + case 'assistant': + return 'Studio.ai'; + case 'tool': + return 'Tool'; + case 'system': + return 'System'; + default: + return role.charAt(0).toUpperCase() + role.slice(1); + } +} + +function formatBytes(size) { + if (!size && size !== 0) return 'unknown'; + if (size < 1024) return `${size} B`; + if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`; + return `${(size / (1024 * 1024)).toFixed(1)} MB`; +} + +async function handleSendMessage() { + if (state.isSending) return; + const text = ui.messageInput.value.trim(); + if (!text && state.attachments.length === 0) return; + + const userMessage = { + role: 'user', + content: text, + timestamp: Date.now(), + }; + + if (state.attachments.length) { + userMessage.attachments = state.attachments.map(({ name, type, size, data }) => ({ + name, + type, + size, + data, + encoding: 'base64', + })); + } + + state.messages.push(userMessage); + renderTranscript(); + ui.messageInput.value = ''; + clearAttachments(); + updatePayloadPreview(); + + try { + state.isSending = true; + setStatus('Contacting agent...', true); + const payload = buildPayload(); + updatePayloadPreview(payload); + const url = buildAgentUrl(); + const response = await dispatchRequest(url, payload); + ingestAgentResponse(response); + setStatus('Idle'); + } catch (error) { + console.error('Agent request failed', error); + setStatus('Error contacting agent', false, true); + const errorMessage = { + role: 'system', + content: formatError(error), + timestamp: Date.now(), + internal: true, + }; + state.messages.push(errorMessage); + renderTranscript(); + updateInspector(error); + } finally { + state.isSending = false; + } +} + +function buildAgentUrl() { + const base = settings.baseUrl.replace(/\/$/, ''); + const endpoint = settings.endpoint || 'invoke'; + return `${base}/agents/${settings.agentId}/${endpoint}`; +} + +function buildPayload() { + const conversation = []; + if (settings.systemPrompt.trim()) { + conversation.push({ role: 'system', content: settings.systemPrompt.trim() }); + } + + state.messages.forEach(message => { + if (!message || !message.role || message.internal) return; + const entry = { + role: message.role, + content: message.content, + }; + + if (Array.isArray(message.attachments) && message.attachments.length) { + entry.attachments = message.attachments.map(item => ({ + name: item.name, + type: item.type, + size: item.size, + encoding: item.encoding || 'base64', + data: item.data, + })); + } + + conversation.push(entry); + }); + + const metadata = safeParseJson(settings.metadata); + const payload = { messages: conversation }; + + if (metadata && typeof metadata === 'object') { + payload.metadata = metadata; + } + + if (settings.threadId) { + payload.threadId = settings.threadId; + } + + if (settings.stream || settings.endpoint === 'stream') { + payload.stream = true; + } + + if (settings.toolTarget && settings.toolTarget !== 'auto') { + payload.control = { preferredFocus: settings.toolTarget }; + } + + return payload; +} + +async function dispatchRequest(url, payload) { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json, text/event-stream, text/plain', + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`${response.status} ${response.statusText}\n${errorText}`); + } + + const contentType = response.headers.get('content-type') || ''; + + if (contentType.includes('text/event-stream')) { + return await consumeEventStream(response.body); + } + + if (contentType.includes('application/json')) { + return await response.json(); + } + + return await response.text(); +} + +async function consumeEventStream(stream) { + if (!stream) { + return { type: 'stream', chunks: [] }; + } + + const reader = stream.getReader(); + const decoder = new TextDecoder('utf-8'); + let buffer = ''; + const chunks = []; + let assistantMessage = null; + + while (true) { + const { value, done } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + + const events = buffer.split('\n\n'); + buffer = events.pop() || ''; + + events.forEach(event => { + const dataLine = event + .split('\n') + .filter(line => line.startsWith('data:')) + .map(line => line.replace(/^data:\s*/, '')) + .join('\n'); + if (!dataLine) return; + + chunks.push(dataLine); + + if (!assistantMessage) { + assistantMessage = { + role: 'assistant', + content: '', + timestamp: Date.now(), + }; + state.messages.push(assistantMessage); + } + + assistantMessage.content += parseStreamChunk(dataLine); + renderTranscript(); + }); + } + + return { + type: 'stream', + chunks: chunks.map(chunk => safeParseJson(chunk) ?? chunk), + }; +} + +function parseStreamChunk(chunk) { + if (!chunk) return ''; + const parsed = safeParseJson(chunk); + if (!parsed) { + return chunk; + } + + if (typeof parsed === 'string') return parsed; + if (parsed.delta?.content) return normaliseContent(parsed.delta.content); + if (parsed.content) return normaliseContent(parsed.content); + if (parsed.output_text) return normaliseContent(parsed.output_text); + return JSON.stringify(parsed); +} + +function ingestAgentResponse(payload) { + if (payload === undefined || payload === null) { + return; + } + + if (payload?.type === 'stream') { + updateInspector(payload.chunks); + updatePayloadPreview(); + return; + } + + updateInspector(payload); + + const messages = extractMessagesFromPayload(payload); + if (!messages.length) { + const assistantMessage = { + role: 'assistant', + content: typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2), + timestamp: Date.now(), + }; + state.messages.push(assistantMessage); + } else { + messages.forEach(message => { + state.messages.push({ + role: message.role || 'assistant', + content: message.content, + timestamp: Date.now(), + }); + }); + } + + renderTranscript(); + updatePayloadPreview(); +} + +function extractMessagesFromPayload(payload) { + const collected = []; + + const maybeMessages = [ + payload?.messages, + payload?.output?.messages, + payload?.response?.messages, + payload?.data?.messages, + ].find(Array.isArray); + + if (Array.isArray(maybeMessages)) { + maybeMessages.forEach(item => { + if (!item) return; + const role = item.role || item.sender || 'assistant'; + const content = normaliseContent(item.content ?? item.text ?? item.value); + if (content) { + collected.push({ role, content }); + } + }); + } + + if (!collected.length && typeof payload === 'object') { + if (payload.output_text) { + collected.push({ role: 'assistant', content: normaliseContent(payload.output_text) }); + } else if (payload.output?.text) { + collected.push({ role: 'assistant', content: normaliseContent(payload.output.text) }); + } else if (payload.output?.content) { + collected.push({ role: 'assistant', content: normaliseContent(payload.output.content) }); + } else if (payload.result) { + collected.push({ role: 'assistant', content: normaliseContent(payload.result) }); + } else if (payload.text) { + collected.push({ role: 'assistant', content: normaliseContent(payload.text) }); + } + } + + return collected; +} + +function normaliseContent(raw) { + if (raw === null || raw === undefined) return ''; + if (typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean') { + return String(raw); + } + if (Array.isArray(raw)) { + return raw.map(normaliseContent).filter(Boolean).join('\n'); + } + if (typeof raw === 'object') { + if (raw.text) return normaliseContent(raw.text); + if (raw.value) return normaliseContent(raw.value); + if (raw.content) return normaliseContent(raw.content); + if (raw.parts) return raw.parts.map(normaliseContent).join('\n'); + if (raw.type === 'output_text' && raw.data) return normaliseContent(raw.data); + if (raw.type === 'text' && raw.data) return normaliseContent(raw.data); + } + try { + return JSON.stringify(raw, null, 2); + } catch (error) { + return String(raw); + } +} + +function updatePayloadPreview(payload = null) { + if (!settings.showRaw) return; + const effectivePayload = payload ?? buildPayload(); + try { + ui.payloadView.textContent = JSON.stringify(effectivePayload, null, 2); + } catch (error) { + ui.payloadView.textContent = 'Unable to serialise payload'; + } +} + +function updateInspector(payload = null) { + if (payload === null || payload === undefined) { + ui.responseView.textContent = ''; + ui.toolEvents.innerHTML = ''; + return; + } + + try { + if (payload instanceof Error) { + ui.responseView.textContent = formatError(payload); + } else if (typeof payload === 'string') { + ui.responseView.textContent = payload; + } else { + ui.responseView.textContent = JSON.stringify(payload, null, 2); + } + } catch (error) { + ui.responseView.textContent = formatError(error); + } + + renderToolEvents(payload); +} + +function renderToolEvents(payload) { + ui.toolEvents.innerHTML = ''; + const events = extractToolEvents(payload); + if (!events.length) { + ui.toolEvents.innerHTML = '

No tool invocations detected.

'; + return; + } + + events.forEach(event => { + const card = document.createElement('article'); + card.className = 'tool-card'; + const header = document.createElement('header'); + header.innerHTML = `${event.name}${event.status}`; + const body = document.createElement('pre'); + body.textContent = event.detail; + card.appendChild(header); + card.appendChild(body); + ui.toolEvents.appendChild(card); + }); +} + +function extractToolEvents(payload) { + const events = []; + + function visit(node) { + if (!node || typeof node !== 'object') return; + if (Array.isArray(node)) { + node.forEach(visit); + return; + } + + if (node.toolName || node.tool || node.name) { + const name = node.toolName || node.tool || node.name; + const input = node.input || node.args || node.arguments || node.payload; + const status = node.status || node.state || (node.success ? 'success' : 'invoked'); + events.push({ + name, + status: status || 'invoked', + detail: normaliseContent(input || node), + }); + } + + Object.values(node).forEach(visit); + } + + visit(payload); + return events; +} + +function handleCopyThreadId() { + if (!settings.threadId) return; + navigator.clipboard?.writeText(settings.threadId).then(() => { + setStatus('Thread ID copied', true); + setTimeout(() => setStatus('Idle'), 1500); + }); +} + +function handleCopyPayload() { + if (!ui.payloadView.textContent) return; + navigator.clipboard?.writeText(ui.payloadView.textContent).then(() => { + setStatus('Payload copied', true); + setTimeout(() => setStatus('Idle'), 1500); + }); +} + +function safeParseJson(value) { + if (!value || typeof value !== 'string') return null; + try { + return JSON.parse(value); + } catch (error) { + return null; + } +} + +function formatError(error) { + if (!error) return 'Unknown error'; + if (error instanceof Error) { + return `${error.name}: ${error.message}`; + } + if (typeof error === 'string') return error; + try { + return JSON.stringify(error, null, 2); + } catch (serializationError) { + return String(error); + } +} + +function setStatus(text, active = false, isError = false) { + ui.status.textContent = text; + ui.status.classList.toggle('active', active && !isError); + ui.status.classList.toggle('error', isError); +} + +function clearAttachments() { + state.attachments = []; + ui.attachmentList.innerHTML = ''; + ui.attachmentInput.value = ''; +} + +function handleAttachmentSelection(event) { + const files = Array.from(event.target.files || []); + if (!files.length) return; + Promise.all(files.map(materialiseFile)) + .then(results => { + state.attachments.push(...results); + renderAttachmentList(); + updatePayloadPreview(); + }) + .catch(error => { + console.error('Unable to read attachments', error); + setStatus('Attachment import failed', false, true); + setTimeout(() => setStatus('Idle'), 2000); + }); +} + +function materialiseFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const base64 = reader.result?.toString().split(',').pop() || ''; + resolve({ + name: file.name, + type: file.type, + size: file.size, + lastModified: file.lastModified, + data: base64, + }); + }; + reader.onerror = () => reject(reader.error); + reader.readAsDataURL(file); + }); +} + +function renderAttachmentList() { + ui.attachmentList.innerHTML = ''; + if (!state.attachments.length) return; + state.attachments.forEach((attachment, index) => { + const badge = document.createElement('span'); + badge.className = 'badge'; + badge.textContent = `${attachment.name} (${formatBytes(attachment.size)})`; + badge.title = `${attachment.type || 'unknown'} • ${formatBytes(attachment.size)}`; + badge.dataset.index = String(index); + badge.addEventListener('click', () => { + state.attachments.splice(index, 1); + renderAttachmentList(); + updatePayloadPreview(); + }); + ui.attachmentList.appendChild(badge); + }); +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..25a00ef --- /dev/null +++ b/public/index.html @@ -0,0 +1,191 @@ + + + + + + Studio.ai - AI Coding Agent + + + + + + +
+ +
+
+
+

Conversation

+

Idle

+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+ Message + +
+ +
+
+ + +
+
+ +
+
+ +
+ +
+ + + + diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..5e7b5ef --- /dev/null +++ b/public/styles.css @@ -0,0 +1,870 @@ +:root { + color-scheme: dark; + + /* Voice UI - Black & White with subtle accents */ + --bg-primary: #000000; + --bg-secondary: #0a0a0a; + --bg-elevated: #121212; + --bg-panel: #1a1a1a; + --surface: #1f1f1f; + --surface-hover: #2a2a2a; + + /* Minimal accent colors */ + --accent-green: #00ff88; + --accent-blue: #0088ff; + --accent-orange: #ff8800; + --accent-subtle: rgba(0, 255, 136, 0.06); + --accent-glow: rgba(0, 255, 136, 0.12); + --accent-strong: rgba(0, 255, 136, 0.20); + + /* Pure black & white text */ + --text-primary: #ffffff; + --text-secondary: #e0e0e0; + --text-muted: #a0a0a0; + --text-dim: #707070; + + /* Minimal borders */ + --border: rgba(255, 255, 255, 0.06); + --border-strong: rgba(255, 255, 255, 0.12); + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5); + --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.6), 0 2px 4px -1px rgba(0, 0, 0, 0.5); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7), 0 4px 6px -2px rgba(0, 0, 0, 0.6); + --shadow-glow: 0 0 20px var(--accent-glow); + + /* Status colors */ + --success: #00ff88; + --warning: #ff8800; + --danger: #ff4444; + --info: #0088ff; + + /* Typography */ + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', 'Consolas', 'Monaco', monospace; + + /* Animations */ + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --transition: 250ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1); +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + margin: 0; + background: + radial-gradient(ellipse at 20% 10%, rgba(0, 255, 136, 0.03), transparent 50%), + radial-gradient(ellipse at 80% 90%, rgba(0, 136, 255, 0.02), transparent 50%), + var(--bg-primary); + color: var(--text-primary); + min-height: 100vh; + overflow: hidden; + font-size: 14px; + line-height: 1.6; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + letter-spacing: -0.02em; +} + +h1 { font-size: 1.5rem; line-height: 1.3; } +h2 { font-size: 1.125rem; line-height: 1.4; } +h3 { font-size: 1rem; line-height: 1.5; } + +p { + color: var(--text-muted); + font-size: 0.875rem; +} + +button, input, textarea, select { + font: inherit; + color: inherit; +} + +/* App Shell */ +.app-shell { + display: grid; + grid-template-columns: 340px 1fr 360px; + grid-template-rows: 100vh; + overflow: hidden; + position: relative; +} + +.app-shell::before { + content: ''; + position: absolute; + inset: 0; + background: + radial-gradient(circle at 30% 20%, rgba(0, 255, 136, 0.02), transparent 40%), + radial-gradient(circle at 70% 80%, rgba(0, 136, 255, 0.02), transparent 40%); + pointer-events: none; + z-index: 0; +} + +/* Sidebar & Inspector */ +.sidebar, .inspector { + background: linear-gradient(145deg, + rgba(18, 18, 18, 0.95), + rgba(10, 10, 10, 0.98) + ); + border-right: 1px solid var(--border); + padding: 32px 28px; + display: flex; + flex-direction: column; + gap: 28px; + backdrop-filter: blur(20px); + overflow-y: auto; + overflow-x: hidden; + position: relative; + z-index: 1; +} + +.sidebar::-webkit-scrollbar, .inspector::-webkit-scrollbar, +.workspace::-webkit-scrollbar, .transcript::-webkit-scrollbar { + width: 6px; +} + +.sidebar::-webkit-scrollbar-track, .inspector::-webkit-scrollbar-track, +.workspace::-webkit-scrollbar-track, .transcript::-webkit-scrollbar-track { + background: transparent; +} + +.sidebar::-webkit-scrollbar-thumb, .inspector::-webkit-scrollbar-thumb, +.workspace::-webkit-scrollbar-thumb, .transcript::-webkit-scrollbar-thumb { + background: var(--surface-hover); + border-radius: 3px; +} + +.inspector { + border-right: 0; + border-left: 1px solid var(--border); +} + +/* Workspace */ +.workspace { + display: flex; + flex-direction: column; + background: rgba(10, 10, 10, 0.85); + backdrop-filter: blur(30px); + position: relative; + z-index: 1; +} + +/* Brand */ +.brand { + display: flex; + align-items: center; + gap: 16px; + padding-bottom: 8px; +} + +.brand-icon { + width: 48px; + height: 48px; + border-radius: 16px; + background: linear-gradient(135deg, var(--accent-green) 0%, var(--accent-blue) 100%); + display: grid; + place-items: center; + box-shadow: var(--shadow-glow), var(--shadow); + position: relative; + overflow: hidden; + transition: transform var(--transition); +} + +.brand-icon::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.1) 100%); + opacity: 0; + transition: opacity var(--transition); +} + +.brand-icon:hover { + transform: scale(1.05); +} + +.brand-icon:hover::before { + opacity: 1; +} + +.brand-icon svg { + width: 28px; + height: 28px; + color: white; + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3)); +} + +.brand h1 { + background: linear-gradient(135deg, var(--accent-green), var(--accent-blue)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-weight: 700; + letter-spacing: -0.03em; +} + +.brand p { + font-size: 0.8125rem; + color: var(--text-dim); + font-weight: 500; +} + +/* Buttons */ +button { + cursor: pointer; + border: none; + outline: none; + transition: all var(--transition); +} + +.primary { + background: linear-gradient(135deg, var(--accent-green), var(--accent-blue)); + color: var(--bg-primary); + padding: 14px 24px; + border-radius: 12px; + font-weight: 600; + font-size: 0.9375rem; + box-shadow: var(--shadow-glow), var(--shadow); + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + position: relative; + overflow: hidden; +} + +.primary::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), transparent); + opacity: 0; + transition: opacity var(--transition); +} + +.primary:hover { + transform: translateY(-2px); + box-shadow: 0 0 30px var(--accent-glow), var(--shadow-lg); +} + +.primary:hover::before { + opacity: 1; +} + +.primary:active { + transform: translateY(0); +} + +.button-icon { + font-size: 1.125rem; + line-height: 1; +} + +.send-button { + min-width: 160px; +} + +/* Panel */ +.panel { + background: linear-gradient(145deg, var(--bg-panel), var(--bg-elevated)); + border-radius: 16px; + padding: 24px 22px; + display: flex; + flex-direction: column; + gap: 20px; + border: 1px solid var(--border); + box-shadow: var(--shadow); + position: relative; +} + +.panel::before { + content: ''; + position: absolute; + inset: 0; + border-radius: 16px; + padding: 1px; + background: linear-gradient(145deg, rgba(0, 255, 136, 0.1), transparent); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + pointer-events: none; +} + +.panel header { + display: flex; + flex-direction: column; + gap: 6px; +} + +.panel header h2 { + font-size: 1rem; + letter-spacing: -0.01em; + color: var(--text-primary); +} + +.panel header p { + font-size: 0.8125rem; + color: var(--text-muted); +} + +/* Fields */ +.field { + display: flex; + flex-direction: column; + gap: 10px; +} + +.field label { + font-size: 0.8125rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); +} + +.field input, +.field textarea, +.field select { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + padding: 12px 14px; + color: var(--text-primary); + outline: none; + transition: all var(--transition); + font-size: 0.9375rem; +} + +.field input::placeholder, +.field textarea::placeholder { + color: var(--text-dim); +} + +.field input:focus, +.field textarea:focus, +.field select:focus { + border-color: var(--accent-green); + box-shadow: 0 0 0 3px var(--accent-subtle); + background: var(--bg-elevated); +} + +.field input:hover, +.field textarea:hover, +.field select:hover { + border-color: var(--border-strong); +} + +.field textarea { + resize: vertical; + min-height: 80px; + font-family: var(--font-mono); + font-size: 0.875rem; + line-height: 1.6; +} + +.field select { + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23a0a0a0'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + background-size: 20px; + padding-right: 40px; +} + +/* Checkbox */ +.field.checkbox { + flex-direction: row; + align-items: center; + gap: 12px; +} + +.field.checkbox input[type='checkbox'] { + width: 20px; + height: 20px; + cursor: pointer; + appearance: none; + border-radius: 6px; + background: var(--surface); + border: 2px solid var(--border-strong); + transition: all var(--transition-fast); + position: relative; + flex-shrink: 0; +} + +.field.checkbox input[type='checkbox']:checked { + background: linear-gradient(135deg, var(--accent-green), var(--accent-blue)); + border-color: transparent; +} + +.field.checkbox input[type='checkbox']:checked::after { + content: '✓'; + position: absolute; + color: var(--bg-primary); + font-size: 14px; + font-weight: 700; + inset: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.field.checkbox label { + text-transform: none; + letter-spacing: normal; + font-weight: 500; + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + color: var(--text-secondary); + font-size: 0.9375rem; +} + +.checkbox-icon { + font-size: 1rem; +} + +/* Input with button */ +.input-with-button { + display: flex; + gap: 8px; +} + +.input-with-button input { + flex: 1; +} + +.input-with-button button { + background: var(--surface-hover); + border: 1px solid var(--border); + border-radius: 10px; + padding: 0 14px; + color: var(--text-muted); + transition: all var(--transition); +} + +.input-with-button button:hover { + background: var(--bg-elevated); + color: var(--text-primary); + border-color: var(--border-strong); +} + +/* Hint text */ +.hint { + font-size: 0.75rem; + color: var(--text-dim); + margin-top: -6px; +} + +/* Topbar */ +.topbar { + padding: 24px 32px; + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + background: linear-gradient(to bottom, var(--bg-secondary), transparent); +} + +.topbar h2 { + color: var(--text-primary); + font-size: 1.125rem; +} + +.status { + font-size: 0.8125rem; + font-weight: 500; + color: var(--text-dim); + display: flex; + align-items: center; + gap: 8px; +} + +.status::before { + content: ''; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--text-dim); + animation: pulse 2s ease-in-out infinite; +} + +.status.active::before { + background: var(--success); +} + +.status.error::before { + background: var(--danger); +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.connection { + display: flex; + gap: 12px; + align-items: flex-end; +} + +.field.compact { + gap: 6px; + min-width: 180px; +} + +.field.compact label { + font-size: 0.6875rem; +} + +.field.compact input, +.field.compact select { + padding: 10px 12px; + font-size: 0.875rem; +} + +/* Transcript */ +.transcript { + flex: 1; + overflow-y: auto; + padding: 24px 32px; + display: flex; + flex-direction: column; + gap: 20px; +} + +.message { + display: flex; + flex-direction: column; + gap: 12px; + padding: 20px 24px; + background: var(--bg-panel); + border-radius: 16px; + border: 1px solid var(--border); + animation: slideIn 0.3s ease-out; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.message header { + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 8px; + border-bottom: 1px solid var(--border); +} + +.message .role { + font-size: 0.875rem; + font-weight: 600; + color: var(--text-secondary); +} + +.message .timestamp { + font-size: 0.75rem; + color: var(--text-dim); + font-family: var(--font-mono); +} + +.message .content { + color: var(--text-primary); + line-height: 1.7; + font-size: 0.9375rem; + white-space: pre-wrap; + word-wrap: break-word; +} + +.message.user { + background: linear-gradient(145deg, var(--surface), var(--bg-elevated)); + border-color: var(--border-strong); +} + +.message.assistant { + background: linear-gradient(145deg, rgba(0, 255, 136, 0.03), var(--bg-panel)); + border-left: 3px solid var(--accent-green); +} + +.message.internal { + background: rgba(255, 68, 68, 0.05); + border-left: 3px solid var(--danger); +} + +/* Composer */ +.composer { + border-top: 1px solid var(--border); + padding: 24px 32px; + background: var(--bg-secondary); + display: flex; + flex-direction: column; + gap: 16px; +} + +.composer-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.composer-header span { + font-size: 0.8125rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); +} + +.composer-header button { + background: transparent; + color: var(--text-dim); + padding: 6px 12px; + border-radius: 8px; + font-size: 0.8125rem; + transition: all var(--transition-fast); +} + +.composer-header button:hover { + background: var(--surface); + color: var(--text-primary); +} + +.composer textarea { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px; + color: var(--text-primary); + outline: none; + resize: vertical; + min-height: 100px; + max-height: 300px; + font-size: 0.9375rem; + line-height: 1.6; + transition: all var(--transition); +} + +.composer textarea::placeholder { + color: var(--text-dim); +} + +.composer textarea:focus { + border-color: var(--accent-green); + box-shadow: 0 0 0 3px var(--accent-subtle); + background: var(--bg-elevated); +} + +.composer-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.attachments { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.attachment-label { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + cursor: pointer; + font-size: 0.875rem; + font-weight: 500; + color: var(--text-secondary); + transition: all var(--transition); +} + +.attachment-label:hover { + background: var(--surface-hover); + border-color: var(--border-strong); + color: var(--text-primary); +} + +.attachment-icon { + font-size: 1rem; +} + +.attachment-list { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background: var(--surface-hover); + border: 1px solid var(--border); + border-radius: 8px; + font-size: 0.75rem; + color: var(--text-muted); + cursor: pointer; + transition: all var(--transition-fast); +} + +.badge:hover { + background: var(--danger); + border-color: transparent; + color: white; +} + +/* Raw Preview */ +.raw-preview { + border-top: 1px solid var(--border); + padding: 20px 32px; + background: var(--bg-secondary); + max-height: 300px; + overflow-y: auto; +} + +.raw-preview header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.raw-preview h3 { + font-size: 0.875rem; + color: var(--text-muted); +} + +.raw-preview button { + background: var(--surface); + border: 1px solid var(--border); + padding: 6px 12px; + border-radius: 8px; + font-size: 0.75rem; + color: var(--text-muted); + transition: all var(--transition-fast); +} + +.raw-preview button:hover { + background: var(--surface-hover); + color: var(--text-primary); +} + +.raw-preview pre { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + padding: 16px; + overflow-x: auto; + font-family: var(--font-mono); + font-size: 0.8125rem; + line-height: 1.6; + color: var(--text-secondary); +} + +/* Code Block */ +.code-block { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + padding: 16px; + overflow-x: auto; + font-family: var(--font-mono); + font-size: 0.8125rem; + line-height: 1.6; + color: var(--text-secondary); + max-height: 400px; + overflow-y: auto; +} + +/* Tool Events */ +.tool-events { + display: flex; + flex-direction: column; + gap: 12px; +} + +.tool-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + padding: 14px; + animation: slideIn 0.3s ease-out; +} + +.tool-card header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid var(--border); +} + +.tool-card header span:first-child { + font-weight: 600; + color: var(--text-primary); + font-size: 0.875rem; +} + +.tool-card header span:last-child { + font-size: 0.75rem; + color: var(--text-dim); + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 3px 8px; + background: var(--bg-panel); + border-radius: 6px; +} + +.tool-card pre { + font-family: var(--font-mono); + font-size: 0.75rem; + line-height: 1.6; + color: var(--text-muted); + white-space: pre-wrap; + word-wrap: break-word; +} + +/* Responsive */ +@media (max-width: 1400px) { + .app-shell { + grid-template-columns: 300px 1fr 320px; + } +} + +@media (max-width: 1200px) { + .app-shell { + grid-template-columns: 280px 1fr 280px; + } + + .sidebar, .inspector { + padding: 24px 20px; + gap: 20px; + } +} diff --git a/src/mastra/agents/coding-agent.ts b/src/mastra/agents/coding-agent.ts index e5a32bf..6003f0d 100644 --- a/src/mastra/agents/coding-agent.ts +++ b/src/mastra/agents/coding-agent.ts @@ -1,6 +1,7 @@ -import { Agent } from '@mastra/core/agent'; -import { LibSQLStore, LibSQLVector } from '@mastra/libsql'; -import { Memory } from '@mastra/memory'; +import { Agent } from '../../studio/agent'; +import { LibSQLStore, LibSQLVector } from '../../studio/libsql'; +import { Memory } from '../../studio/memory'; +import { fastembed } from '../../studio/fastembed'; import { openai } from '@ai-sdk/openai'; import { checkFileExists, @@ -17,7 +18,6 @@ import { writeFile, writeFiles, } from '../tools/e2b'; -import { fastembed } from '@mastra/fastembed'; export const codingAgent = new Agent({ name: 'Coding Agent', @@ -204,14 +204,14 @@ Remember: You are not just a code executor, but a complete development environme runCommand, }, memory: new Memory({ - storage: new LibSQLStore({ url: 'file:../../mastra.db' }), + storage: new LibSQLStore({ url: 'file:../../studio.db' }), options: { threads: { generateTitle: true }, semanticRecall: true, workingMemory: { enabled: true }, }, embedder: fastembed, - vector: new LibSQLVector({ connectionUrl: 'file:../../mastra.db' }), + vector: new LibSQLVector({ connectionUrl: 'file:../../studio.db' }), }), defaultStreamOptions: { maxSteps: 20 }, }); diff --git a/src/mastra/index.ts b/src/mastra/index.ts index 0592afb..86ffa48 100644 --- a/src/mastra/index.ts +++ b/src/mastra/index.ts @@ -1,13 +1,13 @@ -import { Mastra } from '@mastra/core/mastra'; -import { LibSQLStore } from '@mastra/libsql'; -import { PinoLogger } from '@mastra/loggers'; +import { Studio } from '../studio/studio'; +import { LibSQLStore } from '../studio/libsql'; +import { PinoLogger } from '../studio/logger'; import { codingAgent } from './agents/coding-agent'; -export const mastra = new Mastra({ +export const studio = new Studio({ agents: { codingAgent }, - storage: new LibSQLStore({ url: 'file:../../mastra.db' }), + storage: new LibSQLStore({ url: 'file:../../studio.db' }), logger: new PinoLogger({ - name: 'Mastra', + name: 'Studio.ai', level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', }), }); diff --git a/src/mastra/tools/e2b.ts b/src/mastra/tools/e2b.ts index 29c2f29..33b02e8 100644 --- a/src/mastra/tools/e2b.ts +++ b/src/mastra/tools/e2b.ts @@ -1,4 +1,4 @@ -import { createTool } from '@mastra/core/tools'; +import { createTool } from '../../studio/tools'; import z from 'zod'; import { FilesystemEventType, FileType, Sandbox } from '@e2b/code-interpreter'; @@ -189,7 +189,12 @@ export const writeFiles = createTool({ execute: async ({ context }) => { try { const sandbox = await Sandbox.connect(context.sandboxId); - await sandbox.files.write(context.files); + // Explicitly type the files to match E2B's WriteEntry interface + const writeEntries = context.files.map(file => ({ + path: file.path, + data: file.data + })); + await sandbox.files.write(writeEntries); return { success: true, @@ -231,7 +236,8 @@ export const listFiles = createTool({ execute: async ({ context }) => { try { const sandbox = await Sandbox.connect(context.sandboxId); - const fileList = await sandbox.files.list(context.path); + const pathToList = context.path || '/'; + const fileList = await sandbox.files.list(pathToList); fileList.map(f => f.type); @@ -241,7 +247,7 @@ export const listFiles = createTool({ path: file.path, isDirectory: file.type === FileType.DIR, })), - path: context.path, + path: pathToList, }; } catch (e) { return { diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..3936f57 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,134 @@ +import http from 'http'; +import { studio } from './mastra/index'; +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const PORT = process.env.PORT || 8787; + +const server = http.createServer(async (req, res) => { + // Enable CORS + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + if (req.method === 'OPTIONS') { + res.writeHead(200); + res.end(); + return; + } + + const url = new URL(req.url || '', `http://${req.headers.host}`); + + // Serve static files from public directory + if (req.method === 'GET' && !url.pathname.startsWith('/api')) { + try { + let filePath = path.join(__dirname, '../public', url.pathname); + + // Default to index.html for root + if (url.pathname === '/') { + filePath = path.join(__dirname, '../public/index.html'); + } + + const content = await fs.readFile(filePath); + const ext = path.extname(filePath); + + const contentTypes: Record = { + '.html': 'text/html', + '.js': 'application/javascript', + '.css': 'text/css', + '.json': 'application/json', + }; + + res.setHeader('Content-Type', contentTypes[ext] || 'text/plain'); + res.writeHead(200); + res.end(content); + return; + } catch (err) { + res.writeHead(404); + res.end('Not found'); + return; + } + } + + // API routes + if (req.method === 'POST' && url.pathname.startsWith('/api/agents/')) { + const parts = url.pathname.split('/'); + const agentName = parts[3]; + const action = parts[4]; // 'invoke' or 'stream' + + if (!agentName || !action) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Invalid API endpoint' })); + return; + } + + let body = ''; + req.on('data', chunk => { + body += chunk.toString(); + }); + + req.on('end', async () => { + try { + const data = JSON.parse(body); + const agent = studio.agents[agentName]; + + if (!agent) { + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: `Agent ${agentName} not found` })); + return; + } + + if (action === 'invoke') { + const result = await agent.invoke({ + messages: data.messages || [], + threadId: data.threadId, + }); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + } else if (action === 'stream') { + const result = await agent.stream({ + messages: data.messages || [], + threadId: data.threadId, + }); + + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }); + + // Stream the response + for await (const chunk of result.textStream) { + res.write(`data: ${JSON.stringify({ delta: { content: chunk } })}\n\n`); + } + + res.end(); + } else { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Invalid action' })); + } + } catch (err: any) { + console.error('Error processing request:', err); + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message || 'Internal server error' })); + } + }); + + return; + } + + // Default 404 + res.writeHead(404); + res.end('Not found'); +}); + +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}`); + console.log(`API available at http://localhost:${PORT}/api`); + console.log(`UI available at http://localhost:${PORT}`); +}); diff --git a/src/studio/agent.ts b/src/studio/agent.ts new file mode 100644 index 0000000..db90249 --- /dev/null +++ b/src/studio/agent.ts @@ -0,0 +1,119 @@ +import { Tool } from './tools'; +import { Memory } from './memory'; +import { generateText, streamText, CoreMessage } from 'ai'; + +export interface AgentConfig { + name: string; + instructions: string; + model: any; + tools: Record; + memory?: Memory; + defaultStreamOptions?: { + maxSteps?: number; + }; +} + +export class Agent { + name: string; + instructions: string; + model: any; + tools: Record; + memory?: Memory; + defaultStreamOptions?: { + maxSteps?: number; + }; + + constructor(config: AgentConfig) { + this.name = config.name; + this.instructions = config.instructions; + this.model = config.model; + this.tools = config.tools; + this.memory = config.memory; + this.defaultStreamOptions = config.defaultStreamOptions; + } + + async invoke(params: { messages: CoreMessage[]; threadId?: string }) { + const { messages, threadId } = params; + + // Add system instructions + const systemMessage: CoreMessage = { + role: 'system', + content: this.instructions, + }; + + const allMessages = [systemMessage, ...messages]; + + // Store messages in memory if available + if (this.memory && threadId) { + await this.memory.saveMessages(threadId, messages); + } + + // Convert tools to AI SDK format + const toolsForAI: Record = {}; + for (const [name, tool] of Object.entries(this.tools)) { + toolsForAI[name] = { + description: tool.description, + parameters: tool.inputSchema, + execute: async (args: any) => { + return await tool.execute({ context: args }); + }, + }; + } + + // Generate response with tools + const result = await generateText({ + model: this.model, + messages: allMessages, + tools: toolsForAI, + maxSteps: this.defaultStreamOptions?.maxSteps || 10, + }); + + // Store assistant response in memory + if (this.memory && threadId && result.text) { + await this.memory.saveMessages(threadId, [ + { role: 'assistant', content: result.text }, + ]); + } + + return result; + } + + async stream(params: { messages: CoreMessage[]; threadId?: string }) { + const { messages, threadId } = params; + + // Add system instructions + const systemMessage: CoreMessage = { + role: 'system', + content: this.instructions, + }; + + const allMessages = [systemMessage, ...messages]; + + // Store messages in memory if available + if (this.memory && threadId) { + await this.memory.saveMessages(threadId, messages); + } + + // Convert tools to AI SDK format + const toolsForAI: Record = {}; + for (const [name, tool] of Object.entries(this.tools)) { + toolsForAI[name] = { + description: tool.description, + parameters: tool.inputSchema, + execute: async (args: any) => { + return await tool.execute({ context: args }); + }, + }; + } + + // Stream response with tools + const result = await streamText({ + model: this.model, + messages: allMessages, + tools: toolsForAI, + maxSteps: this.defaultStreamOptions?.maxSteps || 10, + }); + + return result; + } +} diff --git a/src/studio/fastembed.ts b/src/studio/fastembed.ts new file mode 100644 index 0000000..be51e68 --- /dev/null +++ b/src/studio/fastembed.ts @@ -0,0 +1,17 @@ +// Placeholder for fastembed - minimal implementation +export const fastembed = { + embed: async (text: string) => { + // Simple hash-based embedding for demonstration + // In production, this would use a real embedding model + const hash = Array.from(text).reduce((acc, char) => { + return ((acc << 5) - acc) + char.charCodeAt(0); + }, 0); + + // Generate a simple vector + const vector = new Array(384).fill(0).map((_, i) => { + return Math.sin((hash + i) / 100); + }); + + return vector; + } +}; diff --git a/src/studio/index.ts b/src/studio/index.ts new file mode 100644 index 0000000..d263704 --- /dev/null +++ b/src/studio/index.ts @@ -0,0 +1,15 @@ +// Core framework exports +export { Studio } from './studio'; +export { Agent } from './agent'; +export { createTool } from './tools'; +export { Memory } from './memory'; +export { LibSQLStore, LibSQLVector } from './libsql'; +export { PinoLogger } from './logger'; +export { fastembed } from './fastembed'; + +// Types +export type { Tool } from './tools'; +export type { AgentConfig } from './agent'; +export type { MemoryOptions, StorageAdapter } from './memory'; +export type { LoggerConfig } from './logger'; +export type { StudioConfig } from './studio'; diff --git a/src/studio/libsql.ts b/src/studio/libsql.ts new file mode 100644 index 0000000..62d3658 --- /dev/null +++ b/src/studio/libsql.ts @@ -0,0 +1,83 @@ +import { StorageAdapter } from './memory'; +import Database from 'better-sqlite3'; +import { resolve } from 'path'; + +export interface LibSQLStoreConfig { + url: string; +} + +export class LibSQLStore implements StorageAdapter { + private db: Database.Database; + private url: string; + + constructor(config: LibSQLStoreConfig) { + this.url = config.url; + // Convert file: URL to actual path + const dbPath = config.url.startsWith('file:') + ? resolve(config.url.replace('file:', '')) + : config.url; + + this.db = new Database(dbPath); + this.initialize(); + } + + private initialize() { + // Create tables if they don't exist + this.db.exec(` + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + thread_id TEXT NOT NULL, + role TEXT NOT NULL, + content TEXT NOT NULL, + timestamp INTEGER DEFAULT (strftime('%s', 'now')) + ); + + CREATE INDEX IF NOT EXISTS idx_thread_id ON messages(thread_id); + `); + } + + async saveMessage(threadId: string, message: any): Promise { + const stmt = this.db.prepare( + 'INSERT INTO messages (thread_id, role, content) VALUES (?, ?, ?)' + ); + stmt.run(threadId, message.role, message.content); + } + + async getMessages(threadId: string): Promise { + const stmt = this.db.prepare( + 'SELECT role, content FROM messages WHERE thread_id = ? ORDER BY timestamp ASC' + ); + return stmt.all(threadId); + } + + async getThreads(): Promise { + const stmt = this.db.prepare( + 'SELECT DISTINCT thread_id FROM messages ORDER BY MAX(timestamp) DESC' + ); + const rows = stmt.all() as Array<{ thread_id: string }>; + return rows.map(row => row.thread_id); + } + + async deleteThread(threadId: string): Promise { + const stmt = this.db.prepare('DELETE FROM messages WHERE thread_id = ?'); + stmt.run(threadId); + } + + close() { + this.db.close(); + } +} + +export class LibSQLVector { + private connectionUrl: string; + + constructor(config: { connectionUrl: string }) { + this.connectionUrl = config.connectionUrl; + } + + // Placeholder for vector operations + async search(query: string, options?: any): Promise { + // In a real implementation, this would perform vector similarity search + return []; + } +} diff --git a/src/studio/logger.ts b/src/studio/logger.ts new file mode 100644 index 0000000..90eb184 --- /dev/null +++ b/src/studio/logger.ts @@ -0,0 +1,45 @@ +export interface LoggerConfig { + name: string; + level?: 'debug' | 'info' | 'warn' | 'error'; +} + +export class PinoLogger { + private name: string; + private level: string; + + constructor(config: LoggerConfig) { + this.name = config.name; + this.level = config.level || 'info'; + } + + debug(message: string, ...args: any[]) { + if (this.shouldLog('debug')) { + console.debug(`[${this.name}] DEBUG:`, message, ...args); + } + } + + info(message: string, ...args: any[]) { + if (this.shouldLog('info')) { + console.info(`[${this.name}] INFO:`, message, ...args); + } + } + + warn(message: string, ...args: any[]) { + if (this.shouldLog('warn')) { + console.warn(`[${this.name}] WARN:`, message, ...args); + } + } + + error(message: string, ...args: any[]) { + if (this.shouldLog('error')) { + console.error(`[${this.name}] ERROR:`, message, ...args); + } + } + + private shouldLog(level: string): boolean { + const levels = ['debug', 'info', 'warn', 'error']; + const currentLevelIndex = levels.indexOf(this.level); + const messageLevelIndex = levels.indexOf(level); + return messageLevelIndex >= currentLevelIndex; + } +} diff --git a/src/studio/memory.ts b/src/studio/memory.ts new file mode 100644 index 0000000..1977784 --- /dev/null +++ b/src/studio/memory.ts @@ -0,0 +1,55 @@ +import { CoreMessage } from 'ai'; + +export interface MemoryOptions { + storage: StorageAdapter; + options?: { + threads?: { + generateTitle?: boolean; + }; + semanticRecall?: boolean; + workingMemory?: { + enabled?: boolean; + }; + }; + embedder?: any; + vector?: any; +} + +export interface StorageAdapter { + saveMessage(threadId: string, message: any): Promise; + getMessages(threadId: string): Promise; + getThreads(): Promise; + deleteThread(threadId: string): Promise; +} + +export class Memory { + private storage: StorageAdapter; + private options: MemoryOptions['options']; + private embedder?: any; + private vector?: any; + + constructor(config: MemoryOptions) { + this.storage = config.storage; + this.options = config.options; + this.embedder = config.embedder; + this.vector = config.vector; + } + + async saveMessages(threadId: string, messages: CoreMessage[]): Promise { + for (const message of messages) { + await this.storage.saveMessage(threadId, message); + } + } + + async getMessages(threadId: string): Promise { + return await this.storage.getMessages(threadId); + } + + async getThreads(): Promise { + return await this.storage.getThreads(); + } + + async deleteThread(threadId: string): Promise { + await this.storage.deleteThread(threadId); + } +} diff --git a/src/studio/studio.ts b/src/studio/studio.ts new file mode 100644 index 0000000..f7435c9 --- /dev/null +++ b/src/studio/studio.ts @@ -0,0 +1,39 @@ +import { Agent } from './agent'; + +export interface StudioConfig { + agents: Record; + storage: any; + logger: any; +} + +export class Studio { + agents: Record; + storage: any; + logger: any; + + constructor(config: StudioConfig) { + this.agents = config.agents; + this.storage = config.storage; + this.logger = config.logger; + } + + getAgent(name: string): Agent | undefined { + return this.agents[name]; + } + + async invoke(agentName: string, params: any) { + const agent = this.agents[agentName]; + if (!agent) { + throw new Error(`Agent ${agentName} not found`); + } + return await agent.invoke(params); + } + + async stream(agentName: string, params: any) { + const agent = this.agents[agentName]; + if (!agent) { + throw new Error(`Agent ${agentName} not found`); + } + return await agent.stream(params); + } +} diff --git a/src/studio/tools.ts b/src/studio/tools.ts new file mode 100644 index 0000000..f793400 --- /dev/null +++ b/src/studio/tools.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; + +export interface Tool { + id: string; + description: string; + inputSchema: z.ZodType; + outputSchema: z.ZodType; + execute: (params: { context: TInput }) => Promise; +} + +export function createTool(config: Tool): Tool { + return config; +} diff --git a/tsconfig.json b/tsconfig.json index 2608d97..e91df7c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,13 +2,14 @@ "compilerOptions": { "target": "ES2022", "module": "ES2022", - "moduleResolution": "bundler", + "moduleResolution": "node", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "strict": true, + "strict": false, "skipLibCheck": true, - "noEmit": true, - "outDir": "dist" + "outDir": "dist", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true }, "include": ["src/**/*"] }