Compare commits

...
3 Commits
Author SHA1 Message Date
Mervin Praison e48afc8993 Release v2.8.4 2025-12-31 11:39:37 +00:00
Mervin Praison 90950f8889 Release v2.8.3 2025-12-31 11:02:57 +00:00
Mervin Praison 56f36e2593 feat: bump version to 1.4.0 and add AI SDK integration dependencies
- Update package.json version from 1.3.6 to 1.4.0
- Update FEATURE_PARITY_TRACKER.json to v1.3.0 (Phase 5 Complete)
- Add AI SDK wrapper features: generateText, streamText, generateObject, streamObject, generateImage, embed, embedMany
- Add server adapters for http, express, hono, fastify, nest
- Add Next.js integration with createRouteHandler and createPagesHandler
- Add MCP client tools: createMCP, getMCPClient, mcpToolsToAITools
2025-12-31 01:39:39 +00:00
119 changed files with 19562 additions and 476 deletions

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.8.2" \
"praisonai>=2.8.4" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.8.2" \
"praisonai>=2.8.4" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.8.2" \
"praisonai>=2.8.4" \
"praisonai[ui]" \
"praisonai[crewai]"
+32
View File
@@ -0,0 +1,32 @@
/**
* Generate Text Example
*
* Demonstrates basic text generation using AI SDK v6 via praisonai-ts.
*
* Run: npx ts-node generate-text.ts
* Required: OPENAI_API_KEY
*/
// Import from local praisonai-ts package
// In production: import { Agent } from 'praisonai';
import { Agent } from '../../../src/praisonai-ts/src';
async function main() {
// Simple text generation with Agent
const agent = new Agent({
instructions: 'You are a helpful assistant that provides concise answers.',
llm: 'openai/gpt-4o-mini',
});
console.log('=== Generate Text Example ===\n');
// Basic chat
const response = await agent.chat('What is the capital of France?');
console.log('Response:', response);
// With context
const response2 = await agent.chat('What is its population?');
console.log('\nFollow-up:', response2);
}
main().catch(console.error);
+91
View File
@@ -0,0 +1,91 @@
/**
* MCP Client Example (AI SDK v6 Parity)
*
* Demonstrates Model Context Protocol client with HTTP/SSE transports.
*
* Run: npx ts-node mcp-client.ts
* Note: Requires an MCP server running
*/
import {
createMCP,
closeMCPClient,
type MCPConfig,
} from '../../../src/praisonai-ts/src';
async function main() {
console.log('=== MCP Client Example ===\n');
// Example configurations for different transports
const configs: Record<string, MCPConfig> = {
// HTTP transport (recommended for production)
http: {
transport: {
type: 'http',
url: 'https://mcp-server.example.com/mcp',
headers: { Authorization: 'Bearer token' },
},
name: 'http-client',
},
// SSE transport
sse: {
transport: {
type: 'sse',
url: 'https://mcp-server.example.com/sse',
},
name: 'sse-client',
},
// Stdio transport (for local servers)
stdio: {
transport: {
type: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-everything'],
},
name: 'stdio-client',
},
};
console.log('MCP Transport Configurations:');
console.log('');
console.log('1. HTTP Transport (recommended):');
console.log(` ${JSON.stringify(configs.http.transport, null, 2)}`);
console.log('');
console.log('2. SSE Transport:');
console.log(` ${JSON.stringify(configs.sse.transport, null, 2)}`);
console.log('');
console.log('3. Stdio Transport (local):');
console.log(` ${JSON.stringify(configs.stdio.transport, null, 2)}`);
console.log('\n--- Example Usage ---\n');
console.log(`
// Create MCP client
const client = await createMCP({
transport: {
type: 'http',
url: 'https://your-mcp-server.com/mcp',
headers: { Authorization: 'Bearer your-token' },
}
});
// Get available tools
const tools = await client.tools();
console.log('Available tools:', Object.keys(tools));
// List resources
const resources = await client.listResources();
console.log('Resources:', resources);
// List prompts
const prompts = await client.listPrompts();
console.log('Prompts:', prompts);
// Close when done
await closeMCPClient('default');
`);
console.log('\n✅ MCP Client demo completed!');
console.log('Note: To run with a real MCP server, uncomment the connection code.');
}
main().catch(console.error);
@@ -0,0 +1,87 @@
/**
* Speech & Transcription Example (AI SDK v6 Parity)
*
* Demonstrates speech generation and transcription wrappers.
*
* Run: npx ts-node speech-transcription.ts
* Required: OPENAI_API_KEY or ELEVENLABS_API_KEY
*/
import {
generateSpeech,
transcribe,
SPEECH_MODELS,
TRANSCRIPTION_MODELS,
} from '../../../src/praisonai-ts/src';
async function main() {
console.log('=== Speech & Transcription Example ===\n');
// Show available models
console.log('1. Available Speech Models:');
Object.entries(SPEECH_MODELS).forEach(([key, value]) => {
console.log(` ${key}: ${value}`);
});
console.log('\n2. Available Transcription Models:');
Object.entries(TRANSCRIPTION_MODELS).forEach(([key, value]) => {
console.log(` ${key}: ${value}`);
});
// Example: Generate speech (requires API key)
console.log('\n3. Speech Generation Example:');
console.log(`
// Generate speech from text
const result = await generateSpeech({
text: 'Hello, this is a test of speech synthesis.',
model: 'openai/tts-1',
voice: 'alloy',
});
// Save to file
await fs.writeFile('output.mp3', result.audio);
console.log('Audio saved to output.mp3');
`);
// Example: Transcribe audio (requires API key)
console.log('4. Transcription Example:');
console.log(`
// Transcribe audio file
const result = await transcribe({
audio: await fs.readFile('audio.mp3'),
model: 'openai/whisper-1',
});
console.log('Transcription:', result.text);
console.log('Language:', result.language);
console.log('Duration:', result.durationInSeconds, 'seconds');
// With segments
if (result.segments) {
result.segments.forEach(seg => {
console.log(\`[\${seg.start}s - \${seg.end}s]: \${seg.text}\`);
});
}
`);
// Live test if API key is available
if (process.env.OPENAI_API_KEY) {
console.log('\n5. Live Speech Generation Test:');
try {
const result = await generateSpeech({
text: 'Hello from PraisonAI!',
model: 'openai/tts-1',
voice: 'alloy',
});
console.log(` ✅ Generated ${result.audio.length} bytes of audio`);
} catch (error: any) {
console.log(` ⚠️ Speech generation failed: ${error.message}`);
}
} else {
console.log('\n5. Live Test: Skipped (OPENAI_API_KEY not set)');
}
console.log('\n✅ Speech & Transcription demo completed!');
}
main().catch(console.error);
+27
View File
@@ -0,0 +1,27 @@
/**
* Stream Text Example
*
* Demonstrates streaming text generation using AI SDK v6 via praisonai-ts.
*
* Run: npx ts-node stream-text.ts
* Required: OPENAI_API_KEY
*/
import { Agent } from '../../../src/praisonai-ts/src';
async function main() {
const agent = new Agent({
instructions: 'You are a helpful assistant that provides detailed answers.',
llm: 'openai/gpt-4o-mini',
stream: true,
});
console.log('=== Stream Text Example ===\n');
console.log('Streaming response:\n');
// Stream the response
const response = await agent.chat('Explain quantum computing in 3 paragraphs.');
console.log('\n\nFinal response:', response);
}
main().catch(console.error);
+78
View File
@@ -0,0 +1,78 @@
/**
* Telemetry Example (AI SDK v6 Parity)
*
* Demonstrates telemetry and tracing with optional OpenTelemetry integration.
*
* Run: npx ts-node telemetry.ts
* Required: OPENAI_API_KEY
*/
import {
configureTelemetry,
isTelemetryEnabled,
createAISpan,
recordEvent,
getEvents,
clearEvents,
Agent,
} from '../../../src/praisonai-ts/src';
async function main() {
console.log('=== Telemetry Example ===\n');
// Configure telemetry
configureTelemetry({
isEnabled: true,
functionId: 'telemetry-demo',
metadata: {
version: '1.0.0',
environment: 'development',
},
});
console.log('1. Telemetry Status:');
console.log(` Enabled: ${isTelemetryEnabled()}`);
// Create a span for an operation
console.log('\n2. Creating AI Span:');
const span = createAISpan('generateText', {
model: 'gpt-4o-mini',
provider: 'openai',
});
try {
// Simulate AI operation
console.log(' Performing AI operation...');
await new Promise(resolve => setTimeout(resolve, 100));
span.setAttribute('tokens', 150);
span.setStatus({ code: 'ok' });
console.log(' Span completed successfully');
} catch (error: any) {
span.recordException(error);
span.setStatus({ code: 'error', message: error.message });
} finally {
span.end();
}
// Record custom events
console.log('\n3. Recording Events:');
recordEvent('agent.started', { agentName: 'demo-agent' });
recordEvent('tool.called', { toolName: 'search', duration: 250 });
recordEvent('agent.completed', { success: true });
const events = getEvents();
console.log(` Recorded ${events.length} events:`);
events.forEach(e => {
console.log(` - ${e.name}: ${JSON.stringify(e.attributes)}`);
});
// Clear events
console.log('\n4. Clearing Events:');
clearEvents();
console.log(` Events after clear: ${getEvents().length}`);
console.log('\n✅ Telemetry demo completed!');
}
main().catch(console.error);
+71
View File
@@ -0,0 +1,71 @@
/**
* Tool Approval Example (AI SDK v6 Parity)
*
* Demonstrates human-in-the-loop tool approval using needsApproval.
*
* Run: npx ts-node tool-approval.ts
* Required: OPENAI_API_KEY
*/
import {
Agent,
withApproval,
getApprovalManager,
createDangerousPatternChecker,
} from '../../../src/praisonai-ts/src';
async function main() {
console.log('=== Tool Approval Example ===\n');
// Set up approval handler
const manager = getApprovalManager();
manager.onApprovalRequest(async (request) => {
console.log(`\n🔐 APPROVAL REQUESTED`);
console.log(` Tool: ${request.toolName}`);
console.log(` Input: ${JSON.stringify(request.input)}`);
console.log(` Reason: ${request.reason || 'No reason provided'}`);
// Auto-approve for demo (in production, prompt user)
console.log(` ✅ Auto-approved for demo\n`);
return true;
});
// Create a tool that always requires approval
const deleteFile = withApproval({
name: 'deleteFile',
description: 'Delete a file from the filesystem',
needsApproval: true, // Always require approval
execute: async (input: { path: string }) => {
console.log(` Executing: Would delete ${input.path}`);
return { success: true, deleted: input.path };
},
});
// Create a tool with conditional approval (dangerous patterns only)
const runCommand = withApproval({
name: 'runCommand',
description: 'Run a shell command',
needsApproval: createDangerousPatternChecker(),
execute: async (input: { command: string }) => {
console.log(` Executing: ${input.command}`);
return { output: 'command executed' };
},
});
// Test the tools
console.log('1. Testing deleteFile (always requires approval):');
const result1 = await deleteFile({ path: '/tmp/test.txt' });
console.log(' Result:', result1);
console.log('\n2. Testing runCommand with safe command (no approval needed):');
const result2 = await runCommand({ command: 'ls -la' });
console.log(' Result:', result2);
console.log('\n3. Testing runCommand with dangerous command (requires approval):');
const result3 = await runCommand({ command: 'rm -rf /tmp/test' });
console.log(' Result:', result3);
console.log('\n✅ Tool approval demo completed!');
}
main().catch(console.error);
+63
View File
@@ -0,0 +1,63 @@
/**
* UI Message Example (AI SDK v6 Parity)
*
* Demonstrates UIMessage protocol for Next.js/React integration.
*
* Run: npx ts-node ui-message.ts
* Required: OPENAI_API_KEY
*/
import {
createTextMessage,
createSystemMessage,
convertToModelMessages,
convertToUIMessages,
validateUIMessages,
hasPendingApprovals,
type UIMessage,
} from '../../../src/praisonai-ts/src';
async function main() {
console.log('=== UI Message Example ===\n');
// Create UI messages
const messages: UIMessage[] = [
createSystemMessage('You are a helpful assistant.'),
createTextMessage('user', 'What is the capital of France?'),
createTextMessage('assistant', 'The capital of France is Paris.'),
createTextMessage('user', 'What is its population?'),
];
console.log('1. Created UI Messages:');
messages.forEach((msg, i) => {
const text = msg.parts.find(p => p.type === 'text');
console.log(` [${i}] ${msg.role}: ${text ? (text as any).text.substring(0, 50) : '...'}`);
});
// Validate messages
console.log('\n2. Validating messages:');
const isValid = validateUIMessages(messages);
console.log(` Valid: ${isValid}`);
// Convert to model messages (for AI SDK)
console.log('\n3. Converting to Model Messages:');
const modelMessages = await convertToModelMessages(messages);
modelMessages.forEach((msg, i) => {
const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
console.log(` [${i}] ${msg.role}: ${content.substring(0, 50)}...`);
});
// Check for pending approvals
console.log('\n4. Checking for pending approvals:');
const hasPending = messages.some(m => hasPendingApprovals(m));
console.log(` Has pending approvals: ${hasPending}`);
// Convert model messages back to UI messages
console.log('\n5. Round-trip conversion:');
const uiMessages = convertToUIMessages(modelMessages);
console.log(` Converted ${uiMessages.length} messages back to UI format`);
console.log('\n✅ UI Message demo completed!');
}
main().catch(console.error);
+49
View File
@@ -0,0 +1,49 @@
/**
* MCP Filesystem Server Example
*
* Demonstrates using MCP to connect to a filesystem server.
*
* Prerequisites:
* npm install praisonai-ts
* export OPENAI_API_KEY=your-api-key
*
* Run:
* npx ts-node filesystem-mcp.ts
*/
import { Agent, createMCP } from '../../../src/praisonai-ts/src';
async function main() {
console.log('=== MCP Filesystem Server Example ===\n');
// Connect to MCP filesystem server
const mcp = await createMCP({
transport: {
type: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '.'],
},
});
console.log('Connected to MCP filesystem server\n');
// Get tools from MCP server
const tools = await mcp.tools();
console.log('Available tools:', Object.keys(tools));
// Create agent with MCP tools
const agent = new Agent({
name: 'FileAgent',
instructions: 'You help manage files using the filesystem tools.',
tools: Object.values(tools),
});
// Ask the agent to list files
const response = await agent.chat('List all TypeScript files in the current directory');
console.log('\nResponse:', response);
// Cleanup
await mcp.close();
}
main().catch(console.error);
+41
View File
@@ -0,0 +1,41 @@
/**
* Multimodal Image Analysis Example
*
* Demonstrates analyzing images with a vision-capable model.
*
* Prerequisites:
* npm install praisonai-ts
* export OPENAI_API_KEY=your-api-key
*
* Run:
* npx ts-node image-analysis.ts
*/
import { Agent } from '../../../src/praisonai-ts/src';
async function main() {
console.log('=== Multimodal Image Analysis Example ===\n');
// Create an agent with a vision-capable model
const agent = new Agent({
name: 'VisionAgent',
llm: 'gpt-4o', // Vision-capable model
instructions: 'You analyze images and describe what you see in detail.',
});
// For multimodal, we describe the image in the prompt
// The agent will use its vision capabilities
const imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/300px-PNG_transparency_demonstration_1.png';
console.log('Analyzing image...\n');
console.log('Image URL:', imageUrl, '\n');
// Ask the agent to analyze (in production, use multimodal message format)
const response = await agent.chat(
`Please analyze this image: ${imageUrl}\nDescribe what you see in detail.`
);
console.log('Analysis:', response);
}
main().catch(console.error);
+50
View File
@@ -0,0 +1,50 @@
# Observability Examples
Examples demonstrating how to use observability integrations with PraisonAI TypeScript.
## Setup
```bash
# Install dependencies
cd /path/to/praisonai-ts
pnpm install
# Set API keys for observability tools
export LANGFUSE_SECRET_KEY=sk-...
export LANGFUSE_PUBLIC_KEY=pk-...
# Or use built-in tools (no API key needed)
```
## Examples
| File | Description |
|------|-------------|
| `basic-tracing.ts` | Basic tracing with memory adapter |
| `langfuse-integration.ts` | Langfuse observability integration |
| `multi-agent-attribution.ts` | Track agent_id/run_id across agents |
| `switchable-tools.ts` | Switch between observability tools via env |
## Running Examples
```bash
# Run with ts-node or tsx
npx tsx examples/js/observability/basic-tracing.ts
npx tsx examples/js/observability/multi-agent-attribution.ts
# With Langfuse (requires API keys)
LANGFUSE_SECRET_KEY=sk-... npx tsx examples/js/observability/langfuse-integration.ts
```
## Supported Tools
| Tool | Env Variable | Package |
|------|--------------|---------|
| Langfuse | `LANGFUSE_SECRET_KEY` | `langfuse` |
| LangSmith | `LANGCHAIN_API_KEY` | `langsmith` |
| LangWatch | `LANGWATCH_API_KEY` | `langwatch` |
| Arize | `ARIZE_API_KEY` | `arize-phoenix` |
| Axiom | `AXIOM_TOKEN` | `@axiomhq/js` |
| Braintrust | `BRAINTRUST_API_KEY` | `braintrust` |
| Helicone | `HELICONE_API_KEY` | `@helicone/helicone` |
| Console | (none) | built-in |
| Memory | (none) | built-in |
@@ -0,0 +1,45 @@
/**
* Basic Tracing Example
*
* Demonstrates basic observability with the built-in memory adapter.
* No external API keys required.
*/
import { Agent, MemoryObservabilityAdapter, setObservabilityAdapter } from 'praisonai';
async function main() {
// Create memory adapter (stores traces in memory)
const obs = new MemoryObservabilityAdapter();
setObservabilityAdapter(obs);
// Create agent with observability enabled
const agent = new Agent({
name: 'TracedAgent',
instructions: 'You are a helpful assistant. Answer questions concisely.'
});
console.log('Running agent with tracing...\n');
// Run some interactions
await agent.chat('What is 2 + 2?');
await agent.chat('What is the capital of France?');
// View traces
const traces = obs.getAllTraces();
console.log('\n=== Trace Summary ===');
console.log(`Total traces: ${traces.length}`);
for (const trace of traces) {
console.log(`\nTrace: ${trace.name}`);
console.log(` Status: ${trace.status}`);
console.log(` Spans: ${trace.spans.length}`);
for (const span of trace.spans) {
const duration = span.endTime ? span.endTime - span.startTime : 'running';
console.log(` - ${span.kind}: ${span.name} (${duration}ms)`);
}
}
}
main().catch(console.error);
@@ -0,0 +1,60 @@
/**
* Multi-Agent Attribution Example
*
* Demonstrates tracking agent_id, run_id, and trace_id across
* multi-agent workflows for proper attribution.
*/
import { Agent, Agents, MemoryObservabilityAdapter, setObservabilityAdapter } from 'praisonai';
async function main() {
// Create memory adapter
const obs = new MemoryObservabilityAdapter();
setObservabilityAdapter(obs);
// Create agents
const researcher = new Agent({
name: 'Researcher',
instructions: 'Research topics and gather information.'
});
const writer = new Agent({
name: 'Writer',
instructions: 'Write clear summaries based on research.'
});
// Create multi-agent workflow
const agents = new Agents({
agents: [researcher, writer],
tasks: [
{ agent: researcher, description: 'Research: {topic}' },
{ agent: writer, description: 'Summarize the research findings' }
]
});
console.log('Running multi-agent workflow with attribution...\n');
await agents.start({ topic: 'TypeScript best practices' });
// View traces with attribution
const traces = obs.getAllTraces();
console.log('\n=== Attribution Report ===');
for (const trace of traces) {
console.log(`\nTrace ID: ${trace.id}`);
console.log(` Name: ${trace.name}`);
if (trace.attribution) {
console.log(` Agent ID: ${trace.attribution.agentId || 'N/A'}`);
console.log(` Run ID: ${trace.attribution.runId || 'N/A'}`);
console.log(` Session ID: ${trace.attribution.sessionId || 'N/A'}`);
}
console.log(` Spans: ${trace.spans.length}`);
for (const span of trace.spans) {
console.log(` - [${span.kind}] ${span.name}`);
}
}
}
main().catch(console.error);
@@ -0,0 +1,40 @@
/**
* Switchable Observability Tools Example
*
* Demonstrates switching between observability tools via environment variable.
* Set OBS_TOOL env var to: langfuse, langsmith, console, memory, noop
*
* Usage:
* OBS_TOOL=console npx tsx switchable-tools.ts
* OBS_TOOL=langfuse LANGFUSE_SECRET_KEY=sk-... npx tsx switchable-tools.ts
*/
import { Agent, createObservabilityAdapter, setObservabilityAdapter } from 'praisonai';
async function main() {
// Get tool from environment (default to memory)
const toolName = process.env.OBS_TOOL || 'memory';
console.log(`Using observability tool: ${toolName}\n`);
// Create adapter dynamically
const obs = await createObservabilityAdapter(toolName as any);
setObservabilityAdapter(obs);
// Create agent
const agent = new Agent({
name: 'ObservedAgent',
instructions: 'You are a helpful assistant.'
});
// Run interaction
console.log('Running agent...\n');
const response = await agent.chat('Hello! What is 2 + 2?');
console.log('Response:', response);
// Flush traces to backend
await obs.flush();
console.log('\nTraces flushed to', toolName);
}
main().catch(console.error);
+47
View File
@@ -0,0 +1,47 @@
# Provider Examples
Examples demonstrating how to use different LLM providers with PraisonAI TypeScript.
## Setup
```bash
# Install dependencies
cd /path/to/praisonai-ts
pnpm install
# Set API keys
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
export GOOGLE_API_KEY=AIza...
```
## Examples
| File | Description |
|------|-------------|
| `multi-provider.ts` | Use multiple providers in one workflow |
| `openai-compatible.ts` | Use OpenAI-compatible APIs (LM Studio, etc.) |
| `local-ollama.ts` | Use local Ollama models |
| `provider-selection.ts` | Dynamic provider selection based on task |
## Running Examples
```bash
# Run with ts-node or tsx
npx tsx examples/js/providers/multi-provider.ts
npx tsx examples/js/providers/openai-compatible.ts
npx tsx examples/js/providers/local-ollama.ts
```
## Environment Variables
| Provider | Variable | Example |
|----------|----------|---------|
| OpenAI | `OPENAI_API_KEY` | `sk-...` |
| Anthropic | `ANTHROPIC_API_KEY` | `sk-ant-...` |
| Google | `GOOGLE_API_KEY` | `AIza...` |
| xAI | `XAI_API_KEY` | `xai-...` |
| Groq | `GROQ_API_KEY` | `gsk_...` |
| Mistral | `MISTRAL_API_KEY` | `...` |
| DeepSeek | `DEEPSEEK_API_KEY` | `...` |
| Ollama | `OLLAMA_BASE_URL` | `http://localhost:11434` |
+40
View File
@@ -0,0 +1,40 @@
/**
* Local Ollama Provider Example
*
* Demonstrates using local Ollama models with PraisonAI.
*
* Setup:
* 1. Install Ollama: https://ollama.ai
* 2. Pull a model: ollama pull llama3.2
* 3. Run this example
*
* Optional env var:
* - OLLAMA_BASE_URL (default: http://localhost:11434)
*/
import { Agent } from 'praisonai';
async function main() {
// Ollama with llama3.2
const ollamaAgent = new Agent({
name: 'LocalLlama',
instructions: 'You are a helpful local AI assistant running on Ollama.',
llm: 'ollama/llama3.2',
llmConfig: {
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434'
}
});
console.log('Testing Ollama local model...\n');
try {
const response = await ollamaAgent.chat('Hello! What model are you running?');
console.log('Response:', response);
} catch (error: any) {
console.log('Note: Ollama not running or model not pulled.');
console.log('Start Ollama and run: ollama pull llama3.2');
console.log('Error:', error.message);
}
}
main().catch(console.error);
+57
View File
@@ -0,0 +1,57 @@
/**
* Multi-Provider Example
*
* Demonstrates using multiple LLM providers in a single workflow.
* Each agent uses a different provider optimized for its task.
*
* Required env vars:
* - OPENAI_API_KEY
* - ANTHROPIC_API_KEY
* - GOOGLE_API_KEY
*/
import { Agent, Agents } from 'praisonai';
async function main() {
// Fast model for quick categorization
const triageAgent = new Agent({
name: 'Triage',
instructions: 'Quickly categorize the user request into: technical, creative, or general.',
llm: 'openai/gpt-4o-mini' // Fast and cheap
});
// Powerful model for complex reasoning
const analysisAgent = new Agent({
name: 'Analyst',
instructions: 'Analyze the request in depth and provide detailed insights.',
llm: 'anthropic/claude-3-5-sonnet' // Best reasoning
});
// Creative model for content generation
const writerAgent = new Agent({
name: 'Writer',
instructions: 'Write a clear, engaging response based on the analysis.',
llm: 'google/gemini-2.0-flash' // Good for creative tasks
});
// Create multi-agent workflow
const agents = new Agents({
agents: [triageAgent, analysisAgent, writerAgent],
tasks: [
{ agent: triageAgent, description: 'Categorize: {input}' },
{ agent: analysisAgent, description: 'Analyze the categorized request' },
{ agent: writerAgent, description: 'Write the final response' }
]
});
console.log('Starting multi-provider workflow...\n');
const result = await agents.start({
input: 'Explain how neural networks learn'
});
console.log('\n=== Final Result ===');
console.log(result);
}
main().catch(console.error);
@@ -0,0 +1,48 @@
/**
* OpenAI-Compatible Provider Example
*
* Demonstrates using any OpenAI-compatible API endpoint.
* Works with LM Studio, vLLM, LocalAI, and other compatible servers.
*
* Required env vars:
* - OPENAI_COMPATIBLE_BASE_URL (e.g., http://localhost:1234/v1)
* - OPENAI_COMPATIBLE_API_KEY (optional, use 'not-needed' for local)
*/
import { Agent } from 'praisonai';
async function main() {
// LM Studio example (local)
const lmStudioAgent = new Agent({
name: 'LocalAssistant',
instructions: 'You are a helpful local AI assistant.',
llm: 'openai-compatible/local-model',
llmConfig: {
baseUrl: process.env.LM_STUDIO_BASE_URL || 'http://localhost:1234/v1',
apiKey: 'not-needed'
}
});
// Custom OpenAI-compatible endpoint
const customAgent = new Agent({
name: 'CustomAssistant',
instructions: 'You are a helpful assistant.',
llm: 'openai-compatible/my-model',
llmConfig: {
baseUrl: process.env.OPENAI_COMPATIBLE_BASE_URL || 'http://localhost:8000/v1',
apiKey: process.env.OPENAI_COMPATIBLE_API_KEY || 'not-needed'
}
});
console.log('Testing OpenAI-compatible endpoint...\n');
try {
const response = await lmStudioAgent.chat('Hello! What model are you?');
console.log('Response:', response);
} catch (error: any) {
console.log('Note: LM Studio not running. Start it with a model loaded.');
console.log('Error:', error.message);
}
}
main().catch(console.error);
+57
View File
@@ -0,0 +1,57 @@
/**
* Basic RAG Agent Example
*
* Demonstrates building a RAG agent with context injection.
*
* Prerequisites:
* npm install praisonai-ts
* export OPENAI_API_KEY=your-api-key
*
* Run:
* npx ts-node basic-rag.ts
*/
import { Agent } from '../../../src/praisonai-ts/src';
// Simulated knowledge base (in production, use a vector store)
const knowledgeBase = [
{ id: '1', content: 'PraisonAI is an AI agent framework for building intelligent applications.' },
{ id: '2', content: 'Agents can use tools to accomplish tasks like web search and calculations.' },
{ id: '3', content: 'RAG combines retrieval with generation for accurate, grounded responses.' },
];
// Simple search function (in production, use vector similarity)
function searchKnowledge(query: string): string[] {
const queryLower = query.toLowerCase();
return knowledgeBase
.filter(doc => doc.content.toLowerCase().includes(queryLower.split(' ')[0]))
.map(doc => doc.content);
}
async function main() {
console.log('=== Basic RAG Agent Example ===\n');
// Create an agent with RAG-style instructions
const agent = new Agent({
name: 'RAGAgent',
instructions: `You are a helpful assistant that answers questions using provided context.
Always base your answers on the context provided.
If the context doesn't contain relevant information, say so.`,
});
// Query with context injection
const query = 'What is PraisonAI?';
console.log(`Q: ${query}`);
// Retrieve relevant context
const context = searchKnowledge(query).join('\n\n');
// Ask the agent with context
const response = await agent.chat(
`Context:\n${context}\n\nQuestion: ${query}`
);
console.log(`A: ${response}\n`);
}
main().catch(console.error);
@@ -0,0 +1,77 @@
/**
* Express Server Adapter Example
*
* Demonstrates deploying an AI agent as an Express HTTP server.
*
* Prerequisites:
* npm install praisonai-ts express
* export OPENAI_API_KEY=your-api-key
*
* Run:
* npx ts-node express-server.ts
*
* Test:
* curl -X POST http://localhost:3000/api/chat \
* -H "Content-Type: application/json" \
* -d '{"message": "Hello!"}'
*/
import { Agent } from '../../../src/praisonai-ts/src';
// Simple HTTP server without Express dependency for this example
import http from 'http';
async function main() {
console.log('=== Express Server Adapter Example ===\n');
// Create an agent
const agent = new Agent({
name: 'APIAgent',
instructions: 'You are a helpful API assistant. Keep responses concise.',
});
// Create HTTP server
const server = http.createServer(async (req, res) => {
// CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
if (req.method === 'POST' && req.url === '/api/chat') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', async () => {
try {
const { message } = JSON.parse(body);
const response = await agent.chat(message);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ response }));
} catch (error: any) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
});
} else {
res.writeHead(404);
res.end('Not Found');
}
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
console.log('\nTest with:');
console.log(` curl -X POST http://localhost:${PORT}/api/chat \\`);
console.log(' -H "Content-Type: application/json" \\');
console.log(' -d \'{"message": "Hello!"}\'');
});
}
main().catch(console.error);
+84
View File
@@ -0,0 +1,84 @@
/**
* Exa Web Search Tool Example
*
* Demonstrates semantic web search with Exa AI.
*
* Prerequisites:
* npm install praisonai-ts @exalabs/ai-sdk
* export EXA_API_KEY=your-api-key
* export OPENAI_API_KEY=your-api-key
*/
// For local development, use relative imports
// For production, use: import { Agent, exaSearch } from 'praisonai-ts'
import { Agent } from '../../../src/praisonai-ts/src';
import { exaSearch } from '../../../src/praisonai-ts/src/tools/builtins';
async function main() {
console.log('=== Exa Web Search Tool Example ===\n');
// Example 1: Basic Semantic Search
console.log('1. Basic Semantic Search');
console.log('------------------------');
const searchAgent = new Agent({
name: 'SemanticSearcher',
instructions: 'You search the web using semantic search and provide detailed summaries.',
tools: [exaSearch({ numResults: 5, type: 'auto' })],
});
const searchResult = await searchAgent.chat('Find the latest AI research breakthroughs');
console.log('Search Result:', searchResult);
console.log();
// Example 2: Company Research
console.log('2. Company Research');
console.log('-------------------');
const companyAgent = new Agent({
name: 'CompanyResearcher',
instructions: 'You research companies and provide analysis.',
tools: [
exaSearch({
type: 'auto',
numResults: 5,
category: 'company',
contents: {
text: { maxCharacters: 1000 },
summary: true,
livecrawl: 'preferred',
},
}),
],
});
const companyResult = await companyAgent.chat(
'Find AI startups in Europe founded after 2020'
);
console.log('Company Result:', companyResult);
console.log();
// Example 3: Academic Paper Search
console.log('3. Academic Paper Search');
console.log('------------------------');
const academicAgent = new Agent({
name: 'AcademicResearcher',
instructions: 'You search for academic papers and summarize findings.',
tools: [
exaSearch({
type: 'neural',
category: 'research paper',
numResults: 5,
includeDomains: ['arxiv.org', 'nature.com', 'science.org'],
}),
],
});
const academicResult = await academicAgent.chat(
'Find recent papers on transformer architectures and attention mechanisms'
);
console.log('Academic Result:', academicResult);
}
main().catch(console.error);
@@ -0,0 +1,86 @@
/**
* Tavily Search Tool Example
*
* Demonstrates web search, content extraction, and crawling with Tavily.
*
* Prerequisites:
* npm install praisonai @tavily/ai-sdk
* export TAVILY_API_KEY=your-api-key
* export OPENAI_API_KEY=your-api-key
*/
// For local development, use relative imports
// For production, use: import { Agent, tavilySearch } from 'praisonai-ts'
import { Agent } from '../../../src/praisonai-ts/src';
import { tavilySearch, tavilyExtract, tavilyCrawl } from '../../../src/praisonai-ts/src/tools/builtins';
async function main() {
console.log('=== Tavily Search Tool Example ===\n');
// Example 1: Basic Web Search
console.log('1. Basic Web Search');
console.log('-------------------');
const searchAgent = new Agent({
name: 'WebSearcher',
instructions: 'You search the web for information and provide concise summaries with sources.',
tools: [tavilySearch({ maxResults: 5, includeAnswer: true })],
});
const searchResult = await searchAgent.chat('What are the latest developments in AI agents?');
console.log('Search Result:', searchResult);
console.log();
// Example 2: News Search with Time Filter
console.log('2. News Search with Time Filter');
console.log('-------------------------------');
const newsAgent = new Agent({
name: 'NewsSearcher',
instructions: 'You search for recent news and summarize key points.',
tools: [
tavilySearch({
topic: 'news',
timeRange: 'week',
maxResults: 5,
}),
],
});
const newsResult = await newsAgent.chat('Latest news about OpenAI');
console.log('News Result:', newsResult);
console.log();
// Example 3: Content Extraction
console.log('3. Content Extraction');
console.log('--------------------');
const extractAgent = new Agent({
name: 'ContentExtractor',
instructions: 'You extract and summarize content from web pages.',
tools: [tavilyExtract()],
});
const extractResult = await extractAgent.chat(
'Extract the main content from https://docs.tavily.com and summarize it'
);
console.log('Extract Result:', extractResult);
console.log();
// Example 4: Website Crawling
console.log('4. Website Crawling');
console.log('------------------');
const crawlAgent = new Agent({
name: 'WebCrawler',
instructions: 'You crawl websites and analyze their structure and content.',
tools: [tavilyCrawl({ maxDepth: 2 })],
});
const crawlResult = await crawlAgent.chat(
'Crawl https://docs.tavily.com and list the main documentation sections'
);
console.log('Crawl Result:', crawlResult);
}
main().catch(console.error);
+18 -8
View File
@@ -1,16 +1,26 @@
{
"version": "1.2.1",
"lastUpdated": "2024-12-26",
"implementationStatus": "Phase 4 Complete - Full Feature Parity",
"version": "1.3.0",
"lastUpdated": "2025-01-15",
"implementationStatus": "Phase 5 Complete - AI SDK Wrapper + Integrations",
"summary": {
"pythonCoreFeatures": 85,
"pythonWrapperFeatures": 45,
"typescriptFeatures": 75,
"gapCount": 10,
"typescriptFeatures": 95,
"gapCount": 5,
"priorityP0": 0,
"priorityP1": 3,
"priorityP2": 5,
"priorityP3": 2
"priorityP1": 1,
"priorityP2": 2,
"priorityP3": 2,
"newInV1_3": {
"aiSdkWrappers": ["generateText", "streamText", "generateObject", "streamObject", "generateImage", "embed", "embedMany"],
"serverAdapters": ["http", "express", "hono", "fastify", "nest"],
"nextjsIntegration": ["createRouteHandler", "createPagesHandler"],
"mcpClient": ["createMCP", "getMCPClient", "mcpToolsToAITools"],
"integrations": ["createSlackBot", "createNLPostgres", "createComputerUse"],
"agentLoop": ["createAgentLoop", "stopAfterSteps", "stopWhenNoToolCalls"],
"multimodal": ["createImagePart", "createFilePart", "createPdfPart", "createMultimodalMessage"],
"middleware": ["createCachingMiddleware", "createLoggingMiddleware", "wrapModel"]
}
},
"pythonCoreSDK": {
"path": "/Users/praison/praisonai-package/src/praisonai-agents/praisonaiagents",
@@ -0,0 +1,209 @@
/**
* AI SDK v6 Parity - Basic Usage Examples
*
* This example demonstrates the new AI SDK v6 compatible features in praisonai-ts.
*/
import {
// Core Agent
Agent,
// Tool Approval
ApprovalManager,
getApprovalManager,
withApproval,
createDangerousPatternChecker,
// UI Messages
UIMessage,
convertToModelMessages,
createTextMessage,
toUIMessageStreamResponse,
// Speech & Transcription
generateSpeech,
transcribe,
// DevTools
enableDevTools,
isDevToolsEnabled,
// Telemetry
configureTelemetry,
createAISpan,
// MCP
createMCP,
} from '../../src';
// ============================================================================
// Example 1: Tool with Approval
// ============================================================================
async function toolApprovalExample() {
console.log('\n=== Tool Approval Example ===\n');
// Create a tool that requires approval
const deleteFile = withApproval({
name: 'deleteFile',
description: 'Delete a file from the filesystem',
needsApproval: true, // Always require approval
execute: async (input: { path: string }) => {
console.log(`Would delete: ${input.path}`);
return { success: true, path: input.path };
},
onDenied: (input) => {
console.log(`Deletion denied for: ${input.path}`);
return { success: false, path: input.path };
},
});
// Create a tool with conditional approval
const runCommand = withApproval({
name: 'runCommand',
description: 'Run a shell command',
needsApproval: createDangerousPatternChecker(), // Only approve dangerous commands
execute: async (input: { command: string }) => {
console.log(`Would run: ${input.command}`);
return { output: 'command output' };
},
});
// Set up approval handler
const manager = getApprovalManager();
manager.onApprovalRequest(async (request) => {
console.log(`\n🔐 Approval requested for: ${request.toolName}`);
console.log(` Input: ${JSON.stringify(request.input)}`);
// Auto-approve for demo
return true;
});
// Test the tools
try {
const result1 = await deleteFile({ path: '/tmp/test.txt' });
console.log('Delete result:', result1);
const result2 = await runCommand({ command: 'ls -la' });
console.log('Command result:', result2);
} catch (error) {
console.error('Error:', error);
}
}
// ============================================================================
// Example 2: UI Messages
// ============================================================================
async function uiMessageExample() {
console.log('\n=== UI Message Example ===\n');
// Create UI messages
const messages: UIMessage[] = [
createTextMessage('user', 'Hello, how are you?'),
createTextMessage('assistant', 'I am doing well, thank you for asking!'),
];
console.log('UI Messages:', JSON.stringify(messages, null, 2));
// Convert to model messages
const modelMessages = await convertToModelMessages(messages);
console.log('\nModel Messages:', JSON.stringify(modelMessages, null, 2));
}
// ============================================================================
// Example 3: DevTools
// ============================================================================
async function devToolsExample() {
console.log('\n=== DevTools Example ===\n');
// Check if DevTools is enabled
console.log('DevTools enabled:', isDevToolsEnabled());
// Enable DevTools (in development)
if (process.env.NODE_ENV === 'development') {
await enableDevTools({
port: 3001,
projectName: 'PraisonAI Demo',
});
console.log('DevTools enabled:', isDevToolsEnabled());
}
}
// ============================================================================
// Example 4: Telemetry
// ============================================================================
async function telemetryExample() {
console.log('\n=== Telemetry Example ===\n');
// Configure telemetry
configureTelemetry({
isEnabled: true,
functionId: 'demo-app',
metadata: {
version: '1.0.0',
environment: 'development',
},
});
// Create a span
const span = createAISpan('generateText', {
model: 'gpt-4o',
provider: 'openai',
});
try {
// Simulate some work
console.log('Performing AI operation...');
await new Promise(resolve => setTimeout(resolve, 100));
span.setAttribute('tokens', 150);
span.setStatus({ code: 'ok' });
} catch (error: any) {
span.recordException(error);
span.setStatus({ code: 'error', message: error.message });
} finally {
span.end();
}
console.log('Telemetry span completed');
}
// ============================================================================
// Example 5: MCP with HTTP Transport
// ============================================================================
async function mcpExample() {
console.log('\n=== MCP Example ===\n');
// Note: This requires an MCP server to be running
console.log('MCP HTTP transport configuration example:');
console.log(`
const client = await createMCP({
transport: {
type: 'http',
url: 'https://mcp-server.example.com/mcp',
headers: { Authorization: 'Bearer token' },
}
});
const tools = await client.tools();
const resources = await client.listResources();
const prompts = await client.listPrompts();
`);
}
// ============================================================================
// Main
// ============================================================================
async function main() {
console.log('🚀 AI SDK v6 Parity Examples\n');
console.log('=' .repeat(50));
await toolApprovalExample();
await uiMessageExample();
await devToolsExample();
await telemetryExample();
await mcpExample();
console.log('\n' + '='.repeat(50));
console.log('✅ All examples completed!');
}
main().catch(console.error);
@@ -0,0 +1,268 @@
/**
* AI SDK Wrapper Demo
*
* Demonstrates the new AI SDK wrapper functions in praisonai-ts.
* These wrappers provide a stable interface over the AI SDK primitives.
*/
import {
// Core text generation
aiGenerateText,
aiStreamText,
// Object generation
aiGenerateObject,
// Image generation
aiGenerateImage,
// Embeddings
aiEmbed,
// Tools
defineTool,
createToolSet,
// Models
getModel,
MODEL_ALIASES,
// Middleware
createCachingMiddleware,
// Multimodal
createImagePart,
createMultimodalMessage,
// MCP
createMCP,
// Server adapters
createExpressHandler,
// Next.js
createRouteHandler,
// Agent loop
createAgentLoop,
stopWhenNoToolCalls,
} from '../../src';
// Also import the Agent class for comparison
import { Agent } from '../../src';
async function main() {
console.log('=== PraisonAI TypeScript - AI SDK Wrapper Demo ===\n');
// -------------------------------------------------------------------------
// 1. Simple Text Generation
// -------------------------------------------------------------------------
console.log('1. Simple Text Generation');
console.log('-------------------------');
try {
const result = await aiGenerateText({
model: 'gpt-4o-mini',
prompt: 'What is 2 + 2? Answer in one word.',
});
console.log('Result:', result.text);
console.log('Usage:', result.usage);
} catch (error: any) {
console.log('Skipped (AI SDK not installed):', error.message);
}
// -------------------------------------------------------------------------
// 2. Streaming Text Generation
// -------------------------------------------------------------------------
console.log('\n2. Streaming Text Generation');
console.log('----------------------------');
try {
const stream = await aiStreamText({
model: 'gpt-4o-mini',
prompt: 'Count from 1 to 5.',
});
process.stdout.write('Streaming: ');
for await (const chunk of stream.textStream) {
process.stdout.write(chunk);
}
console.log('\n');
} catch (error: any) {
console.log('Skipped (AI SDK not installed):', error.message);
}
// -------------------------------------------------------------------------
// 3. Structured Object Generation
// -------------------------------------------------------------------------
console.log('3. Structured Object Generation');
console.log('-------------------------------');
try {
// Using a simple JSON schema (Zod would be better in production)
const result = await aiGenerateObject({
model: 'gpt-4o-mini',
schema: {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' },
city: { type: 'string' },
},
required: ['name', 'age', 'city'],
},
prompt: 'Generate a fictional person.',
});
console.log('Generated object:', result.object);
} catch (error: any) {
console.log('Skipped (AI SDK not installed):', error.message);
}
// -------------------------------------------------------------------------
// 4. Tool Definition
// -------------------------------------------------------------------------
console.log('\n4. Tool Definition');
console.log('------------------');
const weatherTool = defineTool({
description: 'Get weather for a city',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: 'City name' },
},
required: ['city'],
},
execute: async ({ city }) => {
return { temperature: 20, unit: 'celsius', city };
},
});
console.log('Weather tool defined:', weatherTool.description);
// -------------------------------------------------------------------------
// 5. Tool Set Creation
// -------------------------------------------------------------------------
console.log('\n5. Tool Set Creation');
console.log('--------------------');
const tools = createToolSet({
weather: weatherTool,
calculator: defineTool({
description: 'Perform calculations',
parameters: {
type: 'object',
properties: {
expression: { type: 'string' },
},
required: ['expression'],
},
execute: async ({ expression }) => {
return { result: eval(expression) };
},
}),
});
console.log('Tools created:', Object.keys(tools));
// -------------------------------------------------------------------------
// 6. Model Aliases
// -------------------------------------------------------------------------
console.log('\n6. Model Aliases');
console.log('----------------');
console.log('Available aliases:', Object.keys(MODEL_ALIASES).slice(0, 10), '...');
// -------------------------------------------------------------------------
// 7. Multimodal Message Creation
// -------------------------------------------------------------------------
console.log('\n7. Multimodal Message Creation');
console.log('------------------------------');
const imagePart = createImagePart('https://example.com/image.png');
console.log('Image part type:', imagePart.type);
const multimodalMsg = createMultimodalMessage(
'What is in this image?',
['https://example.com/image.png']
);
console.log('Multimodal message role:', multimodalMsg.role);
console.log('Content parts:', multimodalMsg.content.length);
// -------------------------------------------------------------------------
// 8. Agent Loop (Manual Control)
// -------------------------------------------------------------------------
console.log('\n8. Agent Loop (Manual Control)');
console.log('------------------------------');
const loop = createAgentLoop({
model: 'gpt-4o-mini',
system: 'You are a helpful assistant.',
tools: {
greet: defineTool({
description: 'Greet someone',
parameters: {
type: 'object',
properties: { name: { type: 'string' } },
required: ['name'],
},
execute: async ({ name }) => `Hello, ${name}!`,
}),
},
maxSteps: 3,
stopWhen: stopWhenNoToolCalls(),
});
console.log('Agent loop created with max 3 steps');
console.log('Stop condition: when no tool calls');
// -------------------------------------------------------------------------
// 9. Server Handler Creation
// -------------------------------------------------------------------------
console.log('\n9. Server Handler Creation');
console.log('--------------------------');
const expressHandler = createExpressHandler({
handler: async (input) => {
return { text: `Echo: ${input.message}` };
},
streaming: false,
cors: true,
});
console.log('Express handler created');
// -------------------------------------------------------------------------
// 10. Next.js Route Handler
// -------------------------------------------------------------------------
console.log('\n10. Next.js Route Handler');
console.log('-------------------------');
const routeHandler = createRouteHandler({
handler: async (input) => {
return { text: `Response: ${input.prompt}` };
},
streaming: true,
});
console.log('Next.js route handler created');
// -------------------------------------------------------------------------
// 11. Comparison with Agent Class
// -------------------------------------------------------------------------
console.log('\n11. Comparison with Agent Class');
console.log('-------------------------------');
console.log('The Agent class provides a higher-level abstraction:');
console.log('- Automatic tool calling');
console.log('- Session persistence');
console.log('- Multi-agent orchestration');
console.log('');
console.log('The AI SDK wrappers provide lower-level control:');
console.log('- Direct access to generateText/streamText');
console.log('- Manual agent loops');
console.log('- Server adapters for various frameworks');
// -------------------------------------------------------------------------
// Summary
// -------------------------------------------------------------------------
console.log('\n=== Summary ===');
console.log('New AI SDK wrapper exports:');
console.log('- aiGenerateText, aiStreamText');
console.log('- aiGenerateObject, aiStreamObject');
console.log('- aiGenerateImage');
console.log('- aiEmbed, aiEmbedMany');
console.log('- defineTool, createToolSet');
console.log('- createAgentLoop');
console.log('- createHttpHandler, createExpressHandler, createHonoHandler, createFastifyHandler, createNestHandler');
console.log('- createRouteHandler (Next.js)');
console.log('- createMCP (MCP client)');
console.log('- createSlackBot, createNLPostgres, createComputerUse (integrations)');
}
main().catch(console.error);
@@ -0,0 +1,214 @@
/**
* AI SDK Tools Registry Example
*
* Demonstrates how to use the built-in AI SDK tools with PraisonAI agents.
*/
import { Agent } from '../../src/agent';
import { tools, registerBuiltinTools } from '../../src/tools/tools';
import { getToolsRegistry } from '../../src/tools/registry/registry';
import { createLoggingMiddleware, createTimeoutMiddleware, createRedactionMiddleware } from '../../src/tools/registry/middleware';
import type { ToolExecutionContext } from '../../src/tools/registry/types';
// Register all built-in tools
registerBuiltinTools();
// Example 1: Web Research Agent with Tavily
async function webResearchExample() {
console.log('\n=== Web Research Agent ===\n');
const agent = new Agent({
name: 'WebResearcher',
instructions: 'You are a web research assistant. Search the web to find information.',
tools: [tools.tavily()],
});
// Note: Requires TAVILY_API_KEY environment variable
// const result = await agent.run('What are the latest developments in AI?');
// console.log(result.text);
console.log('Web research agent created with Tavily search tool.');
console.log('Set TAVILY_API_KEY to use this agent.');
}
// Example 2: Code Execution Agent
async function codeExecutionExample() {
console.log('\n=== Code Execution Agent ===\n');
const agent = new Agent({
name: 'CodeRunner',
instructions: 'You can execute Python code to solve computational problems.',
tools: [tools.codeExecution()],
});
// Note: Requires Vercel deployment or VERCEL_OIDC_TOKEN
// const result = await agent.run('Calculate the first 10 Fibonacci numbers');
// console.log(result.text);
console.log('Code execution agent created.');
console.log('Deploy to Vercel or set VERCEL_OIDC_TOKEN to use.');
}
// Example 3: Secure Agent with Guardrails
async function secureAgentExample() {
console.log('\n=== Secure Agent with Guardrails ===\n');
const agent = new Agent({
name: 'SecureAssistant',
instructions: 'You are a secure assistant that protects user data.',
tools: [
tools.guard(), // Check for prompt injection
tools.redact(), // Remove PII from responses
tools.verify(), // Verify claims
],
});
// Note: Requires SUPERAGENT_API_KEY
console.log('Secure agent created with guard, redact, and verify tools.');
console.log('Set SUPERAGENT_API_KEY to use security features.');
}
// Example 4: Finance Research Agent with Valyu
async function financeResearchExample() {
console.log('\n=== Finance Research Agent ===\n');
const agent = new Agent({
name: 'FinanceAnalyst',
instructions: 'You are a financial analyst. Research stocks, SEC filings, and economic data.',
tools: [
tools.valyuFinanceSearch(),
tools.valyuSecSearch(),
tools.valyuEconomicsSearch(),
],
});
// Note: Requires VALYU_API_KEY
console.log('Finance research agent created with Valyu tools.');
console.log('Set VALYU_API_KEY to use finance search features.');
}
// Example 5: Multi-Tool Agent
async function multiToolExample() {
console.log('\n=== Multi-Tool Research Agent ===\n');
const agent = new Agent({
name: 'ResearchAssistant',
instructions: 'You are a comprehensive research assistant with access to multiple search tools.',
tools: [
tools.tavily(), // General web search
tools.exa(), // Semantic search
tools.valyuPaperSearch(), // Academic papers
tools.firecrawl(), // Web scraping
],
});
console.log('Multi-tool agent created with 4 different search tools.');
}
// Example 6: Using Middleware
async function middlewareExample() {
console.log('\n=== Registry with Middleware ===\n');
const registry = getToolsRegistry();
// Add logging middleware
registry.use(createLoggingMiddleware());
// Add timeout middleware (30 second timeout)
registry.use(createTimeoutMiddleware(30000));
// Add PII redaction middleware
registry.use(createRedactionMiddleware());
// Set hooks for monitoring
registry.setHooks({
beforeToolCall: (name: string, input: unknown, ctx: ToolExecutionContext) => {
console.log(`[Hook] Starting tool: ${name}`);
},
afterToolCall: (name: string, input: unknown, output: unknown, ctx: ToolExecutionContext) => {
console.log(`[Hook] Completed tool: ${name}`);
},
onError: (name: string, error: Error, ctx: ToolExecutionContext) => {
console.error(`[Hook] Error in tool ${name}:`, error.message);
},
});
console.log('Registry configured with logging, timeout, and redaction middleware.');
}
// Example 7: Custom Tool Registration
async function customToolExample() {
console.log('\n=== Custom Tool Registration ===\n');
// Register a custom tool
const customTool = tools.custom({
id: 'my-calculator',
name: 'calculator',
description: 'Perform basic arithmetic calculations',
parameters: {
type: 'object',
properties: {
expression: {
type: 'string',
description: 'Mathematical expression to evaluate',
},
},
required: ['expression'],
},
execute: async (input: { expression: string }) => {
// Simple eval for demo (use a proper math parser in production)
try {
const result = Function(`"use strict"; return (${input.expression})`)();
return { result, expression: input.expression };
} catch (error) {
return { error: 'Invalid expression', expression: input.expression };
}
},
});
const agent = new Agent({
name: 'Calculator',
instructions: 'You can perform calculations.',
tools: [customTool],
});
console.log('Custom calculator tool registered and agent created.');
}
// Example 8: List Available Tools
async function listToolsExample() {
console.log('\n=== Available Tools ===\n');
const allTools = tools.list();
console.log(`Total registered tools: ${allTools.length}\n`);
for (const tool of allTools) {
console.log(`• ${tool.id} (${tool.displayName})`);
console.log(` Tags: ${tool.tags.join(', ')}`);
console.log(` Package: ${tool.packageName}`);
console.log('');
}
}
// Run all examples
async function main() {
console.log('='.repeat(60));
console.log('AI SDK Tools Registry Examples');
console.log('='.repeat(60));
await webResearchExample();
await codeExecutionExample();
await secureAgentExample();
await financeResearchExample();
await multiToolExample();
await middlewareExample();
await customToolExample();
await listToolsExample();
console.log('\n' + '='.repeat(60));
console.log('Examples completed!');
console.log('='.repeat(60));
}
main().catch(console.error);
@@ -0,0 +1,63 @@
/**
* Tavily Search Tool Example
*
* Demonstrates using Tavily search with PraisonAI agents.
*
* Run with:
* TAVILY_API_KEY=your-key node examples/tools/tavily-search.mjs
*/
import { tavilySearch } from '@tavily/ai-sdk';
async function main() {
console.log('=== Tavily Search Example ===\n');
// Check for API key
if (!process.env.TAVILY_API_KEY) {
console.error('ERROR: TAVILY_API_KEY environment variable is required');
console.error('Get your API key at: https://tavily.com');
process.exit(1);
}
// Create the Tavily search tool
const searchTool = tavilySearch({
apiKey: process.env.TAVILY_API_KEY,
searchDepth: 'basic',
maxResults: 5,
includeAnswer: true,
});
console.log('Tool:', searchTool.description);
console.log('\nSearching for: "latest AI agent frameworks 2024"\n');
try {
// Execute the search
const result = await searchTool.execute(
{ query: 'latest AI agent frameworks 2024' },
{ toolCallId: 'search-1', messages: [] }
);
console.log('=== Results ===\n');
console.log('Query:', result.query);
console.log('Response Time:', result.responseTime, 'ms');
if (result.answer) {
console.log('\nAI Answer:', result.answer);
}
console.log('\nTop Results:');
for (const r of result.results || []) {
console.log(`\n• ${r.title}`);
console.log(` URL: ${r.url}`);
console.log(` Score: ${r.score?.toFixed(3)}`);
console.log(` ${r.content.substring(0, 150)}...`);
}
console.log('\n✅ Search completed successfully!');
} catch (error) {
console.error('❌ Search failed:', error.message);
process.exit(1);
}
}
main();
@@ -0,0 +1,57 @@
/**
* Tavily Search Tool Test
*
* Tests the Tavily search tool with a real API key.
* Run with: TAVILY_API_KEY=your-key npx ts-node examples/tools/tavily-test.ts
*/
import { tavilySearch } from '../../src/tools/builtins/tavily';
async function main() {
console.log('Testing Tavily Search Tool...\n');
// Check for API key
if (!process.env.TAVILY_API_KEY) {
console.error('ERROR: TAVILY_API_KEY environment variable is required');
process.exit(1);
}
console.log('API Key found: ' + process.env.TAVILY_API_KEY.substring(0, 10) + '...\n');
try {
// Create the tool
const tool = tavilySearch({ includeAnswer: true, maxResults: 3 });
console.log('Tool created:', tool.name);
console.log('Description:', tool.description);
console.log('\nExecuting search for "latest AI developments 2024"...\n');
// Execute the search
const result = await tool.execute({ query: 'latest AI developments 2024' });
console.log('=== Search Results ===\n');
if (result.answer) {
console.log('AI Answer:', result.answer.substring(0, 200) + '...\n');
}
console.log(`Found ${result.results.length} results:\n`);
for (const r of result.results) {
console.log(`- ${r.title}`);
console.log(` URL: ${r.url}`);
console.log(` Score: ${r.score}`);
console.log(` Content: ${r.content.substring(0, 100)}...`);
console.log('');
}
console.log('Response time:', result.responseTime, 'ms');
console.log('\n✅ Tavily search test PASSED!');
} catch (error) {
console.error('❌ Test FAILED:', error);
process.exit(1);
}
}
main();
+4 -1
View File
@@ -1,6 +1,6 @@
{
"name": "praisonai",
"version": "1.3.6",
"version": "1.4.0",
"description": "PraisonAI TypeScript AI Agents Framework - Node.js, npm, and Javascript AI Agents Framework",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -72,6 +72,9 @@
"openai": "^4.81.0"
},
"optionalDependencies": {
"@airweave/sdk": ">=0.1.0",
"@mendable/firecrawl-js": ">=1.0.0",
"bedrock-agentcore": ">=0.1.0",
"boxen": "^7.1.1",
"chalk": "^4.1.2",
"cli-table3": "^0.6.3",
+1597 -26
View File
File diff suppressed because it is too large. Load diff
+364
View File
@@ -0,0 +1,364 @@
/**
* Agent Loop - Manual Agent Loop Control
*
* Provides utilities for manual control of agent execution loops.
*/
import type { Message, ToolCallPart, ToolResultPart } from './types';
export interface AgentLoopConfig {
/** Model to use */
model: string;
/** System prompt */
system?: string;
/** Available tools */
tools?: Record<string, AgentTool>;
/** Maximum steps (default: 10) */
maxSteps?: number;
/** Stop condition */
stopWhen?: StopCondition;
/** On step finish callback */
onStepFinish?: (step: AgentStep) => void | Promise<void>;
/** On tool call callback (for approval) */
onToolCall?: (toolCall: ToolCallInfo) => Promise<boolean>;
}
export interface AgentTool {
description: string;
parameters: any;
execute: (args: any) => Promise<any>;
}
export interface AgentStep {
stepNumber: number;
text: string;
toolCalls: ToolCallInfo[];
toolResults: ToolResultInfo[];
usage: { promptTokens: number; completionTokens: number; totalTokens: number };
finishReason: string;
}
export interface ToolCallInfo {
toolCallId: string;
toolName: string;
args: any;
}
export interface ToolResultInfo {
toolCallId: string;
toolName: string;
result: any;
isError?: boolean;
}
export type StopCondition =
| { type: 'stepCount'; count: number }
| { type: 'noToolCalls' }
| { type: 'custom'; check: (step: AgentStep) => boolean };
export interface AgentLoopResult {
text: string;
steps: AgentStep[];
totalUsage: { promptTokens: number; completionTokens: number; totalTokens: number };
finishReason: string;
}
/**
* Create a manual agent loop for fine-grained control.
*
* @example Basic usage
* ```typescript
* const loop = createAgentLoop({
* model: 'gpt-4o',
* system: 'You are a helpful assistant',
* tools: {
* search: {
* description: 'Search the web',
* parameters: z.object({ query: z.string() }),
* execute: async ({ query }) => searchWeb(query)
* }
* },
* maxSteps: 5
* });
*
* const result = await loop.run('Find information about AI');
* ```
*
* @example With approval
* ```typescript
* const loop = createAgentLoop({
* model: 'gpt-4o',
* tools: { ... },
* onToolCall: async (toolCall) => {
* const approved = await askUserForApproval(toolCall);
* return approved;
* }
* });
* ```
*
* @example Step-by-step control
* ```typescript
* const loop = createAgentLoop({ model: 'gpt-4o', tools: { ... } });
*
* // Initialize with a prompt
* loop.addMessage({ role: 'user', content: 'Hello' });
*
* // Run one step at a time
* while (!loop.isComplete()) {
* const step = await loop.step();
* console.log('Step:', step);
*
* // Optionally modify messages or tools between steps
* if (needsMoreContext) {
* loop.addMessage({ role: 'user', content: 'Additional context...' });
* }
* }
*
* const result = loop.getResult();
* ```
*/
export function createAgentLoop(config: AgentLoopConfig): AgentLoop {
return new AgentLoop(config);
}
export class AgentLoop {
private config: AgentLoopConfig;
private messages: Message[] = [];
private steps: AgentStep[] = [];
private currentStep = 0;
private complete = false;
private totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
constructor(config: AgentLoopConfig) {
this.config = {
maxSteps: 10,
...config,
};
// Add system message if provided
if (config.system) {
this.messages.push({ role: 'system', content: config.system });
}
}
/**
* Add a message to the conversation.
*/
addMessage(message: Message): void {
this.messages.push(message);
}
/**
* Get all messages in the conversation.
*/
getMessages(): Message[] {
return [...this.messages];
}
/**
* Check if the loop is complete.
*/
isComplete(): boolean {
return this.complete;
}
/**
* Run a single step of the agent loop.
*/
async step(): Promise<AgentStep> {
if (this.complete) {
throw new Error('Agent loop is already complete');
}
this.currentStep++;
// Check max steps
if (this.currentStep > (this.config.maxSteps || 10)) {
this.complete = true;
throw new Error(`Maximum steps (${this.config.maxSteps}) reached`);
}
// Import and call generateText
const { generateText } = await import('./generate-text');
const result = await generateText({
model: this.config.model,
messages: this.messages as any,
tools: this.config.tools,
maxSteps: 1, // Single step
});
// Build step result
const step: AgentStep = {
stepNumber: this.currentStep,
text: result.text,
toolCalls: result.toolCalls.map(tc => ({
toolCallId: tc.toolCallId,
toolName: tc.toolName,
args: tc.args,
})),
toolResults: result.toolResults.map(tr => ({
toolCallId: tr.toolCallId,
toolName: tr.toolName,
result: tr.result,
})),
usage: result.usage,
finishReason: result.finishReason,
};
// Update total usage
this.totalUsage.promptTokens += result.usage.promptTokens;
this.totalUsage.completionTokens += result.usage.completionTokens;
this.totalUsage.totalTokens += result.usage.totalTokens;
// Add assistant message
if (result.text) {
this.messages.push({ role: 'assistant', content: result.text });
}
// Handle tool calls
if (step.toolCalls.length > 0) {
// Check for approval if callback provided
if (this.config.onToolCall) {
for (const toolCall of step.toolCalls) {
const approved = await this.config.onToolCall(toolCall);
if (!approved) {
this.complete = true;
step.finishReason = 'tool_rejected';
break;
}
}
}
// Add tool call message
const toolCallParts: ToolCallPart[] = step.toolCalls.map(tc => ({
type: 'tool-call',
toolCallId: tc.toolCallId,
toolName: tc.toolName,
args: tc.args,
}));
this.messages.push({ role: 'assistant', content: '', toolCalls: toolCallParts });
// Add tool results
for (const tr of step.toolResults) {
const toolResultPart: ToolResultPart = {
type: 'tool-result',
toolCallId: tr.toolCallId,
toolName: tr.toolName,
result: tr.result,
};
this.messages.push({
role: 'tool',
content: [toolResultPart],
toolCallId: tr.toolCallId,
});
}
}
// Store step
this.steps.push(step);
// Call step finish callback
if (this.config.onStepFinish) {
await this.config.onStepFinish(step);
}
// Check stop conditions
if (this.shouldStop(step)) {
this.complete = true;
}
return step;
}
/**
* Run the full agent loop until completion.
*/
async run(prompt: string): Promise<AgentLoopResult> {
// Add user message
this.addMessage({ role: 'user', content: prompt });
// Run steps until complete
while (!this.isComplete()) {
try {
await this.step();
} catch (error: any) {
if (error.message.includes('Maximum steps')) {
break;
}
throw error;
}
}
return this.getResult();
}
/**
* Get the final result.
*/
getResult(): AgentLoopResult {
const lastStep = this.steps[this.steps.length - 1];
return {
text: lastStep?.text || '',
steps: this.steps,
totalUsage: this.totalUsage,
finishReason: lastStep?.finishReason || 'unknown',
};
}
/**
* Check if the loop should stop.
*/
private shouldStop(step: AgentStep): boolean {
// Check custom stop condition
if (this.config.stopWhen) {
switch (this.config.stopWhen.type) {
case 'stepCount':
return this.currentStep >= this.config.stopWhen.count;
case 'noToolCalls':
return step.toolCalls.length === 0;
case 'custom':
return this.config.stopWhen.check(step);
}
}
// Default: stop when no tool calls
return step.toolCalls.length === 0 && step.finishReason === 'stop';
}
/**
* Reset the agent loop.
*/
reset(): void {
this.messages = [];
this.steps = [];
this.currentStep = 0;
this.complete = false;
this.totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
if (this.config.system) {
this.messages.push({ role: 'system', content: this.config.system });
}
}
}
/**
* Create a stop condition that stops after N steps.
*/
export function stopAfterSteps(count: number): StopCondition {
return { type: 'stepCount', count };
}
/**
* Create a stop condition that stops when no tool calls are made.
*/
export function stopWhenNoToolCalls(): StopCondition {
return { type: 'noToolCalls' };
}
/**
* Create a custom stop condition.
*/
export function stopWhen(check: (step: AgentStep) => boolean): StopCondition {
return { type: 'custom', check };
}
+222
View File
@@ -0,0 +1,222 @@
/**
* DevTools - AI SDK v6 DevTools Integration
*
* Provides optional DevTools integration for debugging AI SDK calls.
* This is an optional dependency that provides visibility into LLM calls.
*/
// ============================================================================
// Types
// ============================================================================
export interface DevToolsConfig {
/** Enable DevTools (default: auto-detect from NODE_ENV) */
enabled?: boolean;
/** DevTools server port (default: 3001) */
port?: number;
/** DevTools server host (default: localhost) */
host?: string;
/** Project name for DevTools UI */
projectName?: string;
/** Custom metadata to include */
metadata?: Record<string, unknown>;
}
export interface DevToolsState {
enabled: boolean;
initialized: boolean;
port?: number;
host?: string;
}
// ============================================================================
// Global State
// ============================================================================
let devToolsState: DevToolsState = {
enabled: false,
initialized: false,
};
let devToolsInstance: unknown = null;
// ============================================================================
// DevTools Functions
// ============================================================================
/**
* Enable AI SDK DevTools for debugging.
*
* @example
* ```typescript
* // Enable in development
* if (process.env.NODE_ENV === 'development') {
* await enableDevTools();
* }
* ```
*
* @example With configuration
* ```typescript
* await enableDevTools({
* port: 3001,
* projectName: 'My AI App'
* });
* ```
*/
export async function enableDevTools(config?: DevToolsConfig): Promise<void> {
if (devToolsState.initialized && devToolsState.enabled) {
return; // Already enabled
}
// Check if we should enable
const shouldEnable = config?.enabled ??
(process.env.NODE_ENV === 'development' || process.env.PRAISONAI_DEVTOOLS === 'true');
if (!shouldEnable) {
return;
}
try {
// Try to import @ai-sdk/devtools (optional dependency)
// @ts-ignore - Optional dependency
const devtools = await import('@ai-sdk/devtools');
if ('enableDevTools' in devtools) {
await (devtools as any).enableDevTools({
port: config?.port,
host: config?.host,
projectName: config?.projectName,
metadata: config?.metadata,
});
devToolsInstance = devtools;
devToolsState = {
enabled: true,
initialized: true,
port: config?.port || 3001,
host: config?.host || 'localhost',
};
console.log(`🔧 AI SDK DevTools enabled at http://${devToolsState.host}:${devToolsState.port}`);
}
} catch (error: any) {
if (error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND') {
console.warn(
'⚠️ @ai-sdk/devtools not installed. Install with:\n' +
' npm install @ai-sdk/devtools\n' +
' or: pnpm add @ai-sdk/devtools'
);
} else {
console.warn('⚠️ Failed to enable DevTools:', error.message);
}
devToolsState = {
enabled: false,
initialized: true,
};
}
}
/**
* Disable DevTools.
*/
export async function disableDevTools(): Promise<void> {
if (!devToolsState.enabled) {
return;
}
try {
if (devToolsInstance && typeof (devToolsInstance as any).disableDevTools === 'function') {
await (devToolsInstance as any).disableDevTools();
}
} catch {
// Ignore errors during disable
}
devToolsState = {
enabled: false,
initialized: true,
};
devToolsInstance = null;
}
/**
* Check if DevTools is enabled.
*/
export function isDevToolsEnabled(): boolean {
return devToolsState.enabled;
}
/**
* Get DevTools state.
*/
export function getDevToolsState(): DevToolsState {
return { ...devToolsState };
}
/**
* Get DevTools URL.
*/
export function getDevToolsUrl(): string | null {
if (!devToolsState.enabled) {
return null;
}
return `http://${devToolsState.host || 'localhost'}:${devToolsState.port || 3001}`;
}
// ============================================================================
// Middleware for DevTools
// ============================================================================
/**
* Create middleware that logs to DevTools.
*
* @example
* ```typescript
* const model = wrapModel(openai('gpt-4o'), createDevToolsMiddleware());
* ```
*/
export function createDevToolsMiddleware(): any {
return {
transformParams: async ({ params }: { params: any }) => {
if (!devToolsState.enabled) {
return params;
}
// Add DevTools metadata
return {
...params,
experimental_telemetry: {
...params.experimental_telemetry,
isEnabled: true,
metadata: {
...params.experimental_telemetry?.metadata,
devtools: true,
},
},
};
},
};
}
// ============================================================================
// Auto-enable in Development
// ============================================================================
/**
* Auto-enable DevTools if in development mode.
* Call this at app startup.
*/
export async function autoEnableDevTools(): Promise<void> {
const isDev = process.env.NODE_ENV === 'development';
const isExplicitlyEnabled = process.env.PRAISONAI_DEVTOOLS === 'true';
const isExplicitlyDisabled = process.env.PRAISONAI_DEVTOOLS === 'false';
if (isExplicitlyDisabled) {
return;
}
if (isDev || isExplicitlyEnabled) {
await enableDevTools();
}
}
+167
View File
@@ -0,0 +1,167 @@
/**
* Embed - AI SDK Wrapper
*
* Provides embed and embedMany functions for text embeddings.
*/
// Lazy load AI SDK
let aiSdk: any = null;
let aiSdkLoaded = false;
async function loadAISDK() {
if (aiSdkLoaded) return aiSdk;
try {
aiSdk = await import('ai');
aiSdkLoaded = true;
return aiSdk;
} catch {
return null;
}
}
export interface EmbedOptions {
/** Model to use (e.g., 'text-embedding-3-small', 'openai/text-embedding-3-large') */
model: string;
/** Text to embed */
value: string;
/** Maximum retries */
maxRetries?: number;
/** Abort signal */
abortSignal?: AbortSignal;
/** Additional headers */
headers?: Record<string, string>;
}
export interface EmbedResult {
/** Embedding vector */
embedding: number[];
/** Token usage */
usage: {
tokens: number;
};
}
export interface EmbedManyOptions {
/** Model to use */
model: string;
/** Texts to embed */
values: string[];
/** Maximum retries */
maxRetries?: number;
/** Abort signal */
abortSignal?: AbortSignal;
/** Additional headers */
headers?: Record<string, string>;
}
export interface EmbedManyResult {
/** Embedding vectors */
embeddings: number[][];
/** Token usage */
usage: {
tokens: number;
};
}
/**
* Resolve model string to AI SDK embedding model instance
*/
async function resolveEmbeddingModel(modelString: string) {
let provider = 'openai';
let modelId = modelString;
if (modelString.includes('/')) {
const parts = modelString.split('/');
provider = parts[0].toLowerCase();
modelId = parts.slice(1).join('/');
} else {
if (modelString.startsWith('text-embedding')) provider = 'openai';
else if (modelString.startsWith('voyage')) provider = 'anthropic';
else if (modelString.startsWith('embed-')) provider = 'cohere';
}
let providerModule: any;
try {
switch (provider) {
case 'openai':
providerModule = await import('@ai-sdk/openai');
return providerModule.openai.embedding(modelId);
case 'google':
providerModule = await import('@ai-sdk/google');
return providerModule.google.embedding(modelId);
default:
providerModule = await import('@ai-sdk/openai');
return providerModule.openai.embedding(modelId);
}
} catch (error: any) {
throw new Error(`Failed to load embedding provider '${provider}': ${error.message}`);
}
}
/**
* Embed a single text using an embedding model.
*
* @example
* ```typescript
* const result = await embed({
* model: 'text-embedding-3-small',
* value: 'Hello, world!'
* });
* console.log(result.embedding); // [0.1, 0.2, ...]
* ```
*/
export async function embed(options: EmbedOptions): Promise<EmbedResult> {
const sdk = await loadAISDK();
if (!sdk) {
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
}
const model = await resolveEmbeddingModel(options.model);
const result = await sdk.embed({
model,
value: options.value,
maxRetries: options.maxRetries,
abortSignal: options.abortSignal,
headers: options.headers,
});
return {
embedding: result.embedding,
usage: result.usage || { tokens: 0 },
};
}
/**
* Embed multiple texts using an embedding model.
*
* @example
* ```typescript
* const result = await embedMany({
* model: 'text-embedding-3-small',
* values: ['Hello', 'World']
* });
* console.log(result.embeddings); // [[0.1, ...], [0.2, ...]]
* ```
*/
export async function embedMany(options: EmbedManyOptions): Promise<EmbedManyResult> {
const sdk = await loadAISDK();
if (!sdk) {
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
}
const model = await resolveEmbeddingModel(options.model);
const result = await sdk.embedMany({
model,
values: options.values,
maxRetries: options.maxRetries,
abortSignal: options.abortSignal,
headers: options.headers,
});
return {
embeddings: result.embeddings,
usage: result.usage || { tokens: 0 },
};
}
+144
View File
@@ -0,0 +1,144 @@
/**
* Generate Image - AI SDK Wrapper
*
* Provides generateImage function for image generation.
*/
// Lazy load AI SDK
let aiSdk: any = null;
let aiSdkLoaded = false;
async function loadAISDK() {
if (aiSdkLoaded) return aiSdk;
try {
aiSdk = await import('ai');
aiSdkLoaded = true;
return aiSdk;
} catch {
return null;
}
}
export interface GenerateImageOptions {
/** Model to use (e.g., 'dall-e-3', 'google/imagen-3') */
model: string;
/** Text prompt for image generation */
prompt: string;
/** Number of images to generate */
n?: number;
/** Image size (e.g., '1024x1024') */
size?: `${number}x${number}`;
/** Aspect ratio (e.g., '16:9') */
aspectRatio?: `${number}:${number}`;
/** Seed for reproducibility */
seed?: number;
/** Maximum retries */
maxRetries?: number;
/** Abort signal */
abortSignal?: AbortSignal;
/** Additional headers */
headers?: Record<string, string>;
/** Provider-specific options */
providerOptions?: Record<string, unknown>;
}
export interface GeneratedImage {
/** Base64-encoded image data */
base64: string;
/** Image as Uint8Array */
uint8Array: Uint8Array;
/** MIME type */
mimeType?: string;
}
export interface GenerateImageResult {
/** Generated images */
images: GeneratedImage[];
/** Warnings */
warnings?: any[];
}
/**
* Resolve model string to AI SDK image model instance
*/
async function resolveImageModel(modelString: string) {
let provider = 'openai';
let modelId = modelString;
if (modelString.includes('/')) {
const parts = modelString.split('/');
provider = parts[0].toLowerCase();
modelId = parts.slice(1).join('/');
} else {
if (modelString.startsWith('imagen')) provider = 'google';
else if (modelString.startsWith('dall-e')) provider = 'openai';
}
let providerModule: any;
try {
switch (provider) {
case 'openai':
providerModule = await import('@ai-sdk/openai');
return providerModule.openai.image(modelId);
case 'google':
providerModule = await import('@ai-sdk/google');
return providerModule.google.image(modelId);
default:
providerModule = await import('@ai-sdk/openai');
return providerModule.openai.image(modelId);
}
} catch (error: any) {
throw new Error(`Failed to load image provider '${provider}': ${error.message}`);
}
}
/**
* Generate images using an image model.
*
* @example Generate with DALL-E
* ```typescript
* const result = await generateImage({
* model: 'dall-e-3',
* prompt: 'A futuristic city at sunset',
* size: '1024x1024'
* });
*
* // Save the image
* fs.writeFileSync('image.png', result.images[0].uint8Array);
* ```
*
* @example Generate with Google Imagen
* ```typescript
* const result = await generateImage({
* model: 'google/imagen-3',
* prompt: 'A beautiful landscape',
* aspectRatio: '16:9'
* });
* ```
*/
export async function generateImage(options: GenerateImageOptions): Promise<GenerateImageResult> {
const sdk = await loadAISDK();
if (!sdk) {
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
}
const model = await resolveImageModel(options.model);
const result = await sdk.generateImage({
model,
prompt: options.prompt,
n: options.n,
size: options.size,
aspectRatio: options.aspectRatio,
seed: options.seed,
maxRetries: options.maxRetries,
abortSignal: options.abortSignal,
headers: options.headers,
providerOptions: options.providerOptions,
});
return {
images: result.images || [],
warnings: result.warnings,
};
}
+252
View File
@@ -0,0 +1,252 @@
/**
* Generate Object - AI SDK Wrapper
*
* Provides generateObject and streamObject functions for structured output generation.
*/
import type { Message } from './types';
// Lazy load AI SDK
let aiSdk: any = null;
let aiSdkLoaded = false;
async function loadAISDK() {
if (aiSdkLoaded) return aiSdk;
try {
aiSdk = await import('ai');
aiSdkLoaded = true;
return aiSdk;
} catch {
return null;
}
}
export interface GenerateObjectOptions<T = unknown> {
/** Model to use */
model: string;
/** Schema for the output (Zod schema or JSON schema) */
schema: any;
/** Schema name for the output */
schemaName?: string;
/** Schema description */
schemaDescription?: string;
/** Output mode: 'object' | 'array' | 'enum' | 'no-schema' */
mode?: 'object' | 'array' | 'enum' | 'no-schema';
/** Simple text prompt */
prompt?: string;
/** Chat messages */
messages?: Message[];
/** System message */
system?: string;
/** Maximum tokens */
maxTokens?: number;
/** Temperature */
temperature?: number;
/** Top P */
topP?: number;
/** Maximum retries */
maxRetries?: number;
/** Abort signal */
abortSignal?: AbortSignal;
/** Additional headers */
headers?: Record<string, string>;
/** Callback on finish */
onFinish?: (result: GenerateObjectResult<T>) => void | Promise<void>;
}
export interface GenerateObjectResult<T = unknown> {
/** Generated object */
object: T;
/** Token usage */
usage: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
/** Finish reason */
finishReason: string;
/** Warnings */
warnings?: any[];
}
export interface StreamObjectOptions<T = unknown> extends GenerateObjectOptions<T> {
/** Callback for partial object updates */
onPartialObject?: (partial: Partial<T>) => void | Promise<void>;
}
export interface StreamObjectResult<T = unknown> {
/** Async iterator for partial objects */
partialObjectStream: AsyncIterable<Partial<T>>;
/** Promise that resolves to the final object */
object: Promise<T>;
/** Promise that resolves to usage */
usage: Promise<{
promptTokens: number;
completionTokens: number;
totalTokens: number;
}>;
/** Promise that resolves to finish reason */
finishReason: Promise<string>;
/** Convert to Response for streaming */
toTextStreamResponse(options?: { headers?: Record<string, string> }): Response;
}
/**
* Resolve model string to AI SDK model instance
*/
async function resolveModel(modelString: string) {
let provider = 'openai';
let modelId = modelString;
if (modelString.includes('/')) {
const parts = modelString.split('/');
provider = parts[0].toLowerCase();
modelId = parts.slice(1).join('/');
} else {
if (modelString.startsWith('claude')) provider = 'anthropic';
else if (modelString.startsWith('gemini')) provider = 'google';
}
let providerModule: any;
try {
switch (provider) {
case 'openai':
providerModule = await import('@ai-sdk/openai');
return providerModule.openai(modelId);
case 'anthropic':
providerModule = await import('@ai-sdk/anthropic');
return providerModule.anthropic(modelId);
case 'google':
providerModule = await import('@ai-sdk/google');
return providerModule.google(modelId);
default:
providerModule = await import('@ai-sdk/openai');
return providerModule.openai(modelId);
}
} catch (error: any) {
throw new Error(`Failed to load provider '${provider}': ${error.message}`);
}
}
/**
* Generate a structured object using a language model.
*
* @example With Zod schema
* ```typescript
* import { z } from 'zod';
*
* const result = await generateObject({
* model: 'gpt-4o',
* schema: z.object({
* name: z.string(),
* age: z.number()
* }),
* prompt: 'Generate a person'
* });
* console.log(result.object); // { name: 'John', age: 30 }
* ```
*
* @example With JSON schema
* ```typescript
* const result = await generateObject({
* model: 'gpt-4o',
* schema: {
* type: 'object',
* properties: {
* name: { type: 'string' },
* age: { type: 'number' }
* },
* required: ['name', 'age']
* },
* prompt: 'Generate a person'
* });
* ```
*/
export async function generateObject<T = unknown>(
options: GenerateObjectOptions<T>
): Promise<GenerateObjectResult<T>> {
const sdk = await loadAISDK();
if (!sdk) {
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
}
const model = await resolveModel(options.model);
const result = await sdk.generateObject({
model,
schema: options.schema,
schemaName: options.schemaName,
schemaDescription: options.schemaDescription,
mode: options.mode,
prompt: options.prompt,
messages: options.messages,
system: options.system,
maxTokens: options.maxTokens,
temperature: options.temperature,
topP: options.topP,
maxRetries: options.maxRetries,
abortSignal: options.abortSignal,
headers: options.headers,
onFinish: options.onFinish,
});
return {
object: result.object as T,
usage: result.usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
finishReason: result.finishReason || 'stop',
warnings: result.warnings,
};
}
/**
* Stream a structured object using a language model.
*
* @example Streaming partial objects
* ```typescript
* import { z } from 'zod';
*
* const result = await streamObject({
* model: 'gpt-4o',
* schema: z.object({
* story: z.string(),
* characters: z.array(z.string())
* }),
* prompt: 'Write a short story',
* onPartialObject: (partial) => {
* console.log('Partial:', partial);
* }
* });
*
* const finalObject = await result.object;
* ```
*/
export async function streamObject<T = unknown>(
options: StreamObjectOptions<T>
): Promise<StreamObjectResult<T>> {
const sdk = await loadAISDK();
if (!sdk) {
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
}
const model = await resolveModel(options.model);
const result = sdk.streamObject({
model,
schema: options.schema,
schemaName: options.schemaName,
schemaDescription: options.schemaDescription,
mode: options.mode,
prompt: options.prompt,
messages: options.messages,
system: options.system,
maxTokens: options.maxTokens,
temperature: options.temperature,
topP: options.topP,
maxRetries: options.maxRetries,
abortSignal: options.abortSignal,
headers: options.headers,
onFinish: options.onFinish,
});
return result as StreamObjectResult<T>;
}
+354
View File
@@ -0,0 +1,354 @@
/**
* Generate Text - AI SDK Wrapper
*
* Provides generateText and streamText functions that wrap the AI SDK.
* These are the primary text generation primitives.
*/
import type { Message } from './types';
// Lazy load AI SDK to avoid import errors when not installed
let aiSdk: any = null;
let aiSdkLoaded = false;
async function loadAISDK() {
if (aiSdkLoaded) return aiSdk;
try {
aiSdk = await import('ai');
aiSdkLoaded = true;
return aiSdk;
} catch {
return null;
}
}
export interface GenerateTextOptions {
/** Model to use (e.g., 'gpt-4o', 'claude-3-sonnet', 'openai/gpt-4o') */
model: string;
/** Simple text prompt */
prompt?: string;
/** Chat messages (alternative to prompt) */
messages?: Message[];
/** System message */
system?: string;
/** Tools available to the model */
tools?: Record<string, any>;
/** Tool choice strategy */
toolChoice?: 'auto' | 'none' | 'required' | { type: 'tool'; toolName: string };
/** Maximum tokens to generate */
maxTokens?: number;
/** Temperature (0-2) */
temperature?: number;
/** Top P (0-1) */
topP?: number;
/** Top K */
topK?: number;
/** Presence penalty */
presencePenalty?: number;
/** Frequency penalty */
frequencyPenalty?: number;
/** Stop sequences */
stopSequences?: string[];
/** Seed for reproducibility */
seed?: number;
/** Maximum retries */
maxRetries?: number;
/** Abort signal */
abortSignal?: AbortSignal;
/** Additional headers */
headers?: Record<string, string>;
/** Maximum steps for tool calling */
maxSteps?: number;
/** Callback on step finish */
onStepFinish?: (step: StepResult) => void | Promise<void>;
/** Callback on finish */
onFinish?: (result: GenerateTextResult) => void | Promise<void>;
}
export interface StepResult {
text: string;
toolCalls: ToolCall[];
toolResults: ToolResult[];
usage: TokenUsage;
finishReason: string;
}
export interface ToolCall {
toolCallId: string;
toolName: string;
args: any;
}
export interface ToolResult {
toolCallId: string;
toolName: string;
result: any;
}
export interface TokenUsage {
promptTokens: number;
completionTokens: number;
totalTokens: number;
}
export interface GenerateTextResult {
/** Generated text */
text: string;
/** Tool calls made */
toolCalls: ToolCall[];
/** Tool results */
toolResults: ToolResult[];
/** Token usage */
usage: TokenUsage;
/** Finish reason */
finishReason: string;
/** All steps (for multi-step) */
steps: StepResult[];
/** Response messages */
responseMessages: Message[];
/** Warnings */
warnings?: any[];
}
export interface StreamTextOptions extends GenerateTextOptions {
/** Callback for each text chunk */
onChunk?: (chunk: TextStreamPart) => void | Promise<void>;
}
export interface TextStreamPart {
type: 'text-delta' | 'tool-call' | 'tool-result' | 'finish' | 'error';
textDelta?: string;
toolCall?: ToolCall;
toolResult?: ToolResult;
finishReason?: string;
usage?: TokenUsage;
error?: Error;
}
export interface StreamTextResult {
/** Async iterator for text chunks */
textStream: AsyncIterable<string>;
/** Full text stream with all events */
fullStream: AsyncIterable<TextStreamPart>;
/** Promise that resolves to the final text */
text: Promise<string>;
/** Promise that resolves to tool calls */
toolCalls: Promise<ToolCall[]>;
/** Promise that resolves to tool results */
toolResults: Promise<ToolResult[]>;
/** Promise that resolves to usage */
usage: Promise<TokenUsage>;
/** Promise that resolves to finish reason */
finishReason: Promise<string>;
/** Promise that resolves to all steps */
steps: Promise<StepResult[]>;
/** Promise that resolves to response messages */
responseMessages: Promise<Message[]>;
/** Convert to Response object for streaming */
toDataStreamResponse(options?: { headers?: Record<string, string> }): Response;
/** Pipe to a writable stream */
pipeDataStreamToResponse(response: any, options?: { headers?: Record<string, string> }): void;
}
/**
* Resolve model string to AI SDK model instance
*/
async function resolveModel(modelString: string) {
const sdk = await loadAISDK();
if (!sdk) {
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
}
// Parse provider/model format
let provider = 'openai';
let modelId = modelString;
if (modelString.includes('/')) {
const parts = modelString.split('/');
provider = parts[0].toLowerCase();
modelId = parts.slice(1).join('/');
} else {
// Infer provider from model name
if (modelString.startsWith('claude')) {
provider = 'anthropic';
} else if (modelString.startsWith('gemini')) {
provider = 'google';
} else if (modelString.startsWith('gpt') || modelString.startsWith('o1') || modelString.startsWith('o3')) {
provider = 'openai';
}
}
// Load provider
let providerModule: any;
try {
switch (provider) {
case 'openai':
providerModule = await import('@ai-sdk/openai');
return providerModule.openai(modelId);
case 'anthropic':
providerModule = await import('@ai-sdk/anthropic');
return providerModule.anthropic(modelId);
case 'google':
providerModule = await import('@ai-sdk/google');
return providerModule.google(modelId);
default:
// Try OpenAI-compatible
providerModule = await import('@ai-sdk/openai');
return providerModule.openai(modelId);
}
} catch (error: any) {
throw new Error(`Failed to load provider '${provider}': ${error.message}. Install with: npm install @ai-sdk/${provider}`);
}
}
/**
* Generate text using a language model.
*
* @example Simple prompt
* ```typescript
* const result = await generateText({
* model: 'gpt-4o',
* prompt: 'What is the capital of France?'
* });
* console.log(result.text);
* ```
*
* @example Chat messages
* ```typescript
* const result = await generateText({
* model: 'claude-3-sonnet',
* messages: [
* { role: 'user', content: 'Hello!' }
* ]
* });
* ```
*
* @example With tools
* ```typescript
* const result = await generateText({
* model: 'gpt-4o',
* prompt: 'What is the weather in Paris?',
* tools: {
* getWeather: {
* description: 'Get weather for a city',
* parameters: z.object({ city: z.string() }),
* execute: async ({ city }) => `Weather in ${city}: 20°C`
* }
* },
* maxSteps: 5
* });
* ```
*/
export async function generateText(options: GenerateTextOptions): Promise<GenerateTextResult> {
const sdk = await loadAISDK();
if (!sdk) {
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
}
// Handle both string model names and pre-resolved model objects
const model = typeof options.model === 'string'
? await resolveModel(options.model)
: options.model;
const result = await sdk.generateText({
model,
prompt: options.prompt,
messages: options.messages,
system: options.system,
tools: options.tools,
toolChoice: options.toolChoice,
maxTokens: options.maxTokens,
temperature: options.temperature,
topP: options.topP,
topK: options.topK,
presencePenalty: options.presencePenalty,
frequencyPenalty: options.frequencyPenalty,
stopSequences: options.stopSequences,
seed: options.seed,
maxRetries: options.maxRetries,
abortSignal: options.abortSignal,
headers: options.headers,
maxSteps: options.maxSteps,
onStepFinish: options.onStepFinish,
onFinish: options.onFinish,
});
return {
text: result.text,
toolCalls: result.toolCalls || [],
toolResults: result.toolResults || [],
usage: result.usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
finishReason: result.finishReason || 'stop',
steps: result.steps || [],
responseMessages: result.responseMessages || [],
warnings: result.warnings,
};
}
/**
* Stream text using a language model.
*
* @example Simple streaming
* ```typescript
* const result = await streamText({
* model: 'gpt-4o',
* prompt: 'Write a poem about AI'
* });
*
* for await (const chunk of result.textStream) {
* process.stdout.write(chunk);
* }
* ```
*
* @example With tools and multi-step
* ```typescript
* const result = await streamText({
* model: 'gpt-4o',
* prompt: 'Search for AI news and summarize',
* tools: { webSearch },
* maxSteps: 5,
* onChunk: (chunk) => console.log(chunk)
* });
*
* const text = await result.text;
* ```
*/
export async function streamText(options: StreamTextOptions): Promise<StreamTextResult> {
const sdk = await loadAISDK();
if (!sdk) {
throw new Error('AI SDK not available. Install with: npm install ai @ai-sdk/openai');
}
// Handle both string model names and pre-resolved model objects
const model = typeof options.model === 'string'
? await resolveModel(options.model)
: options.model;
const result = sdk.streamText({
model,
prompt: options.prompt,
messages: options.messages,
system: options.system,
tools: options.tools,
toolChoice: options.toolChoice,
maxTokens: options.maxTokens,
temperature: options.temperature,
topP: options.topP,
topK: options.topK,
presencePenalty: options.presencePenalty,
frequencyPenalty: options.frequencyPenalty,
stopSequences: options.stopSequences,
seed: options.seed,
maxRetries: options.maxRetries,
abortSignal: options.abortSignal,
headers: options.headers,
maxSteps: options.maxSteps,
onStepFinish: options.onStepFinish,
onFinish: options.onFinish,
onChunk: options.onChunk ? async (chunk: any) => {
await options.onChunk!(chunk as TextStreamPart);
} : undefined,
});
return result as StreamTextResult;
}
+243
View File
@@ -0,0 +1,243 @@
/**
* AI SDK Wrapper Module
*
* This module provides a stable wrapper over the AI SDK (aisdk) primitives.
* It isolates aisdk imports and provides typed wrappers to ensure future
* aisdk upgrades don't break praisonai-ts users.
*
* @module ai
*/
// Re-export core execution functions
export {
generateText,
streamText,
type GenerateTextOptions,
type GenerateTextResult,
type StreamTextOptions,
type StreamTextResult,
type TextStreamPart,
} from './generate-text';
export {
generateObject,
streamObject,
type GenerateObjectOptions,
type GenerateObjectResult,
type StreamObjectOptions,
type StreamObjectResult,
} from './generate-object';
export {
generateImage,
type GenerateImageOptions,
type GenerateImageResult,
} from './generate-image';
export {
embed,
embedMany,
type EmbedOptions,
type EmbedResult,
type EmbedManyResult,
} from './embed';
// Re-export tool utilities
export {
defineTool,
createToolSet,
functionToTool,
type ToolDefinition,
type ToolExecuteFunction,
type ToolInput,
type ToolOutput,
} from './tools';
// Re-export model utilities
export {
createModel,
getModel,
parseModel,
MODEL_ALIASES,
listModelAliases,
hasModelAlias,
resolveModelAlias,
type ModelConfig,
type ModelId,
} from './models';
// Re-export middleware
export {
createCachingMiddleware,
createLoggingMiddleware,
wrapModel,
applyMiddleware,
clearCache,
getCacheStats,
type Middleware,
type MiddlewareConfig,
type MiddlewareRequest,
type MiddlewareResponse,
} from './middleware';
// Re-export multimodal utilities
export {
createImagePart,
createFilePart,
createPdfPart,
createTextPart,
createMultimodalMessage,
toMessageContent,
base64ToUint8Array,
uint8ArrayToBase64,
isUrl,
isDataUrl,
type InputPart,
type ImagePart,
type FilePart,
type PdfPart,
type TextPart,
} from './multimodal';
// Re-export MCP client
export {
createMCP,
getMCPClient,
closeMCPClient,
closeAllMCPClients,
mcpToolsToAITools,
type MCPConfig,
type MCPClient,
type MCPTool,
type MCPResource,
type MCPPrompt,
} from './mcp';
// Re-export server adapters
export {
createHttpHandler,
createExpressHandler,
createHonoHandler,
createFastifyHandler,
createNestHandler,
type ServerHandler,
type ServerHandlerConfig,
} from './server';
// Re-export React/Next.js utilities (lazy loaded)
export {
createRouteHandler,
createPagesHandler,
type RouteHandlerConfig,
type UseChatConfig,
} from './nextjs';
// Re-export agent loop utilities
export {
createAgentLoop,
AgentLoop,
stopAfterSteps,
stopWhenNoToolCalls,
stopWhen,
type AgentLoopConfig,
type AgentStep,
type AgentLoopResult,
type StopCondition,
} from './agent-loop';
// Re-export UI message utilities
export {
convertToModelMessages,
convertToUIMessages,
validateUIMessages,
safeValidateUIMessages,
createTextMessage,
createSystemMessage,
hasPendingApprovals,
getToolsNeedingApproval,
createApprovalResponse,
toUIMessageStreamResponse,
pipeUIMessageStreamToResponse,
type UIMessage,
type UIMessagePart,
type TextUIPart,
type ReasoningUIPart,
type ToolUIPart,
type FileUIPart,
type DataUIPart,
type ModelMessage,
type UIMessageStreamOptions,
} from './ui-message';
// Re-export tool approval utilities
export {
ApprovalManager,
getApprovalManager,
setApprovalManager,
withApproval,
createCLIApprovalPrompt,
ToolApprovalDeniedError,
ToolApprovalTimeoutError,
DANGEROUS_PATTERNS,
isDangerous,
createDangerousPatternChecker,
type ToolApprovalConfig,
type ToolApprovalRequest,
type ToolApprovalResponse,
type ApprovalState,
type ApprovalHandler,
} from './tool-approval';
// Re-export speech and transcription
export {
generateSpeech,
transcribe,
SPEECH_MODELS,
TRANSCRIPTION_MODELS,
type GenerateSpeechOptions,
type GenerateSpeechResult,
type TranscribeOptions,
type TranscribeResult,
type TranscriptionSegment,
} from './speech';
// Re-export DevTools
export {
enableDevTools,
disableDevTools,
isDevToolsEnabled,
getDevToolsState,
getDevToolsUrl,
createDevToolsMiddleware,
autoEnableDevTools,
type DevToolsConfig,
type DevToolsState,
} from './devtools';
// Re-export OAuth provider for MCP
export type { OAuthClientProvider } from './mcp';
// Re-export telemetry utilities
export {
configureTelemetry,
getTelemetrySettings,
enableTelemetry as enableAITelemetry,
disableTelemetry as disableAITelemetry,
isTelemetryEnabled,
initOpenTelemetry,
getTracer,
createAISpan,
withSpan,
createTelemetryMiddleware,
recordEvent,
getEvents,
clearEvents,
createTelemetrySettings,
type TelemetrySettings,
type Tracer,
type Span,
type SpanOptions,
type SpanKind,
type SpanStatus,
type TelemetryEvent,
} from './telemetry';
+308
View File
@@ -0,0 +1,308 @@
/**
* MCP (Model Context Protocol) - AI SDK Wrapper
*
* Provides MCP client utilities for connecting to MCP servers and using their tools.
*/
export interface MCPConfig {
/** Transport configuration */
transport: MCPTransportConfig;
/** Client name */
name?: string;
/** Client version */
version?: string;
/** Callback for uncaught errors */
onUncaughtError?: (error: unknown) => void;
}
export type MCPTransportConfig =
| { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
| { type: 'sse'; url: string; headers?: Record<string, string>; authProvider?: OAuthClientProvider }
| { type: 'http'; url: string; headers?: Record<string, string>; authProvider?: OAuthClientProvider }
| { type: 'websocket'; url: string; headers?: Record<string, string> };
/**
* OAuth client provider for MCP authentication.
*/
export interface OAuthClientProvider {
/** Get the current access token */
getAccessToken(): Promise<string | null>;
/** Refresh the access token */
refreshToken?(): Promise<string | null>;
/** Handle OAuth redirect */
handleRedirect?(url: string): Promise<void>;
}
export interface MCPClient {
/** Get tools from the MCP server */
tools(): Promise<Record<string, MCPTool>>;
/** List available resources */
listResources(): Promise<MCPResource[]>;
/** Read a resource */
readResource(uri: string): Promise<MCPResourceContent>;
/** List available prompts */
listPrompts(): Promise<MCPPrompt[]>;
/** Get a prompt */
getPrompt(name: string, args?: Record<string, string>): Promise<MCPPromptResult>;
/** Close the connection */
close(): Promise<void>;
}
export interface MCPTool {
/** Tool name */
name: string;
/** Tool description */
description?: string;
/** Input schema */
inputSchema: any;
/** Execute the tool */
execute: (args: any) => Promise<any>;
}
export interface MCPResource {
/** Resource URI */
uri: string;
/** Resource name */
name: string;
/** Resource description */
description?: string;
/** MIME type */
mimeType?: string;
}
export interface MCPResourceContent {
/** Content URI */
uri: string;
/** Content MIME type */
mimeType?: string;
/** Text content */
text?: string;
/** Binary content (base64) */
blob?: string;
}
export interface MCPPrompt {
/** Prompt name */
name: string;
/** Prompt description */
description?: string;
/** Arguments */
arguments?: Array<{
name: string;
description?: string;
required?: boolean;
}>;
}
export interface MCPPromptResult {
/** Prompt description */
description?: string;
/** Messages */
messages: Array<{
role: 'user' | 'assistant';
content: { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string };
}>;
}
// Connection pool for MCP clients
const clientPool = new Map<string, MCPClient>();
/**
* Create an MCP client.
*
* @example Stdio transport (local server)
* ```typescript
* const client = await createMCP({
* transport: {
* type: 'stdio',
* command: 'npx',
* args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/dir']
* }
* });
*
* const tools = await client.tools();
* ```
*
* @example SSE transport (remote server)
* ```typescript
* const client = await createMCP({
* transport: {
* type: 'sse',
* url: 'https://mcp-server.example.com/sse'
* }
* });
* ```
*/
export async function createMCP(config: MCPConfig): Promise<MCPClient> {
// Try to use AI SDK MCP client (optional dependency)
try {
// @ts-ignore - Optional dependency
const mcpModule = await import('@ai-sdk/mcp');
const transport = createTransport(config.transport);
const client = await mcpModule.createMCPClient({
transport,
name: config.name,
version: config.version,
onUncaughtError: config.onUncaughtError,
});
return {
tools: async () => {
const toolSet = await client.tools();
return toolSet as Record<string, MCPTool>;
},
listResources: async () => {
const result = await client.listResources();
return result.resources || [];
},
readResource: async (uri: string) => {
const result = await client.readResource({ uri });
return result.contents?.[0] || { uri };
},
listPrompts: async () => {
const result = await client.listPrompts();
return result.prompts || [];
},
getPrompt: async (name: string, args?: Record<string, string>) => {
const result = await client.getPrompt({ name, arguments: args });
return result as MCPPromptResult;
},
close: async () => {
await client.close();
},
};
} catch (error: any) {
// Fall back to native implementation
return createNativeMCPClient(config);
}
}
/**
* Create transport configuration for AI SDK MCP.
*/
function createTransport(config: MCPTransportConfig): any {
switch (config.type) {
case 'stdio':
return {
type: 'stdio',
command: config.command,
args: config.args,
env: config.env,
};
case 'sse':
return {
type: 'sse',
url: config.url,
headers: config.headers,
authProvider: config.authProvider,
};
case 'http':
return {
type: 'http',
url: config.url,
headers: config.headers,
authProvider: config.authProvider,
};
case 'websocket':
return {
type: 'websocket',
url: config.url,
headers: config.headers,
};
default:
throw new Error(`Unknown transport type: ${(config as any).type}`);
}
}
/**
* Native MCP client implementation (fallback).
*/
async function createNativeMCPClient(config: MCPConfig): Promise<MCPClient> {
// This is a simplified native implementation
// In production, this would use the @modelcontextprotocol/sdk directly
const tools: Record<string, MCPTool> = {};
const resources: MCPResource[] = [];
const prompts: MCPPrompt[] = [];
return {
tools: async () => tools,
listResources: async () => resources,
readResource: async (uri: string) => ({ uri }),
listPrompts: async () => prompts,
getPrompt: async (name: string) => ({ messages: [] }),
close: async () => {},
};
}
/**
* Get or create a pooled MCP client.
*
* @example
* ```typescript
* const client = await getMCPClient('filesystem', {
* transport: { type: 'stdio', command: 'npx', args: ['-y', '@mcp/server-fs'] }
* });
* ```
*/
export async function getMCPClient(key: string, config: MCPConfig): Promise<MCPClient> {
if (clientPool.has(key)) {
return clientPool.get(key)!;
}
const client = await createMCP(config);
clientPool.set(key, client);
return client;
}
/**
* Close and remove a pooled MCP client.
*/
export async function closeMCPClient(key: string): Promise<void> {
const client = clientPool.get(key);
if (client) {
await client.close();
clientPool.delete(key);
}
}
/**
* Close all pooled MCP clients.
*/
export async function closeAllMCPClients(): Promise<void> {
for (const [key, client] of clientPool) {
await client.close();
clientPool.delete(key);
}
}
/**
* Convert MCP tools to AI SDK tool format.
*
* @example
* ```typescript
* const client = await createMCP({ ... });
* const mcpTools = await client.tools();
* const aiTools = mcpToolsToAITools(mcpTools);
*
* const result = await generateText({
* model: 'gpt-4o',
* prompt: 'List files in the current directory',
* tools: aiTools
* });
* ```
*/
export function mcpToolsToAITools(mcpTools: Record<string, MCPTool>): Record<string, any> {
const aiTools: Record<string, any> = {};
for (const [name, tool] of Object.entries(mcpTools)) {
aiTools[name] = {
description: tool.description || `MCP tool: ${name}`,
parameters: tool.inputSchema,
execute: tool.execute,
};
}
return aiTools;
}
+252
View File
@@ -0,0 +1,252 @@
/**
* Middleware - AI SDK Wrapper
*
* Provides middleware utilities for caching, logging, and model wrapping.
*/
export interface Middleware {
/** Middleware name */
name: string;
/** Transform request before sending */
transformRequest?: (request: MiddlewareRequest) => Promise<MiddlewareRequest> | MiddlewareRequest;
/** Transform response after receiving */
transformResponse?: (response: MiddlewareResponse) => Promise<MiddlewareResponse> | MiddlewareResponse;
/** Handle errors */
onError?: (error: Error) => Promise<void> | void;
}
export interface MiddlewareRequest {
model: string;
prompt?: string;
messages?: any[];
system?: string;
tools?: Record<string, any>;
[key: string]: any;
}
export interface MiddlewareResponse {
text?: string;
object?: any;
toolCalls?: any[];
usage?: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
[key: string]: any;
}
export interface MiddlewareConfig {
/** Enable caching */
cache?: boolean;
/** Cache TTL in seconds */
cacheTTL?: number;
/** Enable logging */
logging?: boolean;
/** Log level */
logLevel?: 'debug' | 'info' | 'warn' | 'error';
/** Custom middlewares */
middlewares?: Middleware[];
}
// In-memory cache for middleware
const responseCache = new Map<string, { response: any; timestamp: number }>();
/**
* Create a caching middleware.
*
* @example
* ```typescript
* const cachingMiddleware = createCachingMiddleware({
* ttl: 3600, // 1 hour
* keyGenerator: (request) => `${request.model}:${request.prompt}`
* });
* ```
*/
export function createCachingMiddleware(options?: {
ttl?: number;
keyGenerator?: (request: MiddlewareRequest) => string;
storage?: {
get: (key: string) => Promise<any>;
set: (key: string, value: any, ttl?: number) => Promise<void>;
};
}): Middleware {
const ttl = options?.ttl ?? 3600;
const keyGenerator = options?.keyGenerator ?? defaultKeyGenerator;
const storage = options?.storage;
return {
name: 'caching',
transformRequest: async (request) => {
const key = keyGenerator(request);
if (storage) {
const cached = await storage.get(key);
if (cached) {
(request as any).__cached = cached;
(request as any).__cacheHit = true;
}
} else {
const cached = responseCache.get(key);
if (cached && Date.now() - cached.timestamp < ttl * 1000) {
(request as any).__cached = cached.response;
(request as any).__cacheHit = true;
}
}
(request as any).__cacheKey = key;
return request;
},
transformResponse: async (response) => {
const key = (response as any).__cacheKey;
if (key && !(response as any).__cacheHit) {
if (storage) {
await storage.set(key, response, ttl);
} else {
responseCache.set(key, { response, timestamp: Date.now() });
}
}
return response;
},
};
}
/**
* Create a logging middleware.
*
* @example
* ```typescript
* const loggingMiddleware = createLoggingMiddleware({
* level: 'debug',
* onRequest: (request) => console.log('Request:', request),
* onResponse: (response) => console.log('Response:', response)
* });
* ```
*/
export function createLoggingMiddleware(options?: {
level?: 'debug' | 'info' | 'warn' | 'error';
onRequest?: (request: MiddlewareRequest) => void;
onResponse?: (response: MiddlewareResponse) => void;
onError?: (error: Error) => void;
}): Middleware {
const level = options?.level ?? 'info';
const log = (msg: string, data?: any) => {
const timestamp = new Date().toISOString();
console[level](`[${timestamp}] ${msg}`, data ?? '');
};
return {
name: 'logging',
transformRequest: async (request) => {
log('AI Request', { model: request.model, hasPrompt: !!request.prompt, hasMessages: !!request.messages });
options?.onRequest?.(request);
return request;
},
transformResponse: async (response) => {
log('AI Response', { hasText: !!response.text, hasObject: !!response.object, usage: response.usage });
options?.onResponse?.(response);
return response;
},
onError: async (error) => {
log('AI Error', { message: error.message });
options?.onError?.(error);
},
};
}
/**
* Wrap a model with middleware.
*
* @example
* ```typescript
* const wrappedModel = wrapModel(model, [
* createCachingMiddleware(),
* createLoggingMiddleware()
* ]);
* ```
*/
export function wrapModel(model: any, middlewares: Middleware[]): any {
// This is a placeholder - actual implementation would wrap the model's
// doGenerate and doStream methods with middleware transformations
return {
...model,
__middlewares: middlewares,
};
}
/**
* Apply middleware to a request/response cycle.
*/
export async function applyMiddleware(
middlewares: Middleware[],
request: MiddlewareRequest,
execute: (req: MiddlewareRequest) => Promise<MiddlewareResponse>
): Promise<MiddlewareResponse> {
// Transform request through all middlewares
let transformedRequest = request;
for (const middleware of middlewares) {
if (middleware.transformRequest) {
transformedRequest = await middleware.transformRequest(transformedRequest);
}
}
// Check for cache hit
if ((transformedRequest as any).__cacheHit) {
return (transformedRequest as any).__cached;
}
// Execute the actual request
let response: MiddlewareResponse;
try {
response = await execute(transformedRequest);
(response as any).__cacheKey = (transformedRequest as any).__cacheKey;
} catch (error: any) {
// Handle errors through middlewares
for (const middleware of middlewares) {
if (middleware.onError) {
await middleware.onError(error);
}
}
throw error;
}
// Transform response through all middlewares (in reverse order)
let transformedResponse = response;
for (const middleware of [...middlewares].reverse()) {
if (middleware.transformResponse) {
transformedResponse = await middleware.transformResponse(transformedResponse);
}
}
return transformedResponse;
}
/**
* Default cache key generator.
*/
function defaultKeyGenerator(request: MiddlewareRequest): string {
const parts = [
request.model,
request.prompt || '',
JSON.stringify(request.messages || []),
request.system || '',
];
return parts.join(':').slice(0, 256);
}
/**
* Clear the response cache.
*/
export function clearCache(): void {
responseCache.clear();
}
/**
* Get cache statistics.
*/
export function getCacheStats(): { size: number; keys: string[] } {
return {
size: responseCache.size,
keys: Array.from(responseCache.keys()),
};
}
+241
View File
@@ -0,0 +1,241 @@
/**
* Models - AI SDK Wrapper
*
* Provides model aliases and utilities for model resolution.
*/
/**
* Model aliases for common models across providers.
* Maps friendly names to provider/model format.
*/
export const MODEL_ALIASES: Record<string, string> = {
// OpenAI
'gpt-4o': 'openai/gpt-4o',
'gpt-4o-mini': 'openai/gpt-4o-mini',
'gpt-4-turbo': 'openai/gpt-4-turbo',
'gpt-4': 'openai/gpt-4',
'gpt-3.5-turbo': 'openai/gpt-3.5-turbo',
'gpt-5': 'openai/gpt-5',
'o1': 'openai/o1',
'o1-mini': 'openai/o1-mini',
'o1-preview': 'openai/o1-preview',
'o3': 'openai/o3',
'o3-mini': 'openai/o3-mini',
// Anthropic
'claude-4': 'anthropic/claude-4-sonnet-20250514',
'claude-4-opus': 'anthropic/claude-4-opus-20250514',
'claude-4-sonnet': 'anthropic/claude-4-sonnet-20250514',
'claude-3.7-sonnet': 'anthropic/claude-3-7-sonnet-20250219',
'claude-3.5-sonnet': 'anthropic/claude-3-5-sonnet-20241022',
'claude-3-sonnet': 'anthropic/claude-3-sonnet-20240229',
'claude-3-opus': 'anthropic/claude-3-opus-20240229',
'claude-3-haiku': 'anthropic/claude-3-haiku-20240307',
// Google
'gemini-3': 'google/gemini-3.0-pro',
'gemini-2.5-pro': 'google/gemini-2.5-pro-preview-06-05',
'gemini-2.5-flash': 'google/gemini-2.5-flash-preview-05-20',
'gemini-2.0-flash': 'google/gemini-2.0-flash',
'gemini-1.5-pro': 'google/gemini-1.5-pro',
'gemini-1.5-flash': 'google/gemini-1.5-flash',
// DeepSeek
'deepseek-r1': 'deepseek/deepseek-reasoner',
'deepseek-v3': 'deepseek/deepseek-chat',
'deepseek-v3.2': 'deepseek/deepseek-chat-v3.2',
// Meta Llama
'llama-3.1': 'groq/llama-3.1-70b-versatile',
'llama-3.1-8b': 'groq/llama-3.1-8b-instant',
'llama-3.1-70b': 'groq/llama-3.1-70b-versatile',
'llama-3.1-405b': 'together/meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo',
// Embedding models
'text-embedding-3-small': 'openai/text-embedding-3-small',
'text-embedding-3-large': 'openai/text-embedding-3-large',
'text-embedding-004': 'google/text-embedding-004',
// Image models
'dall-e-3': 'openai/dall-e-3',
'dall-e-2': 'openai/dall-e-2',
'imagen-3': 'google/imagen-3.0-generate-002',
};
export type ModelId = keyof typeof MODEL_ALIASES | string;
export interface ModelConfig {
/** Provider name */
provider: string;
/** Model ID */
modelId: string;
/** Original model string */
original: string;
}
/**
* Parse a model string into provider and model ID.
*
* @example
* ```typescript
* parseModel('gpt-4o') // { provider: 'openai', modelId: 'gpt-4o', original: 'gpt-4o' }
* parseModel('anthropic/claude-3-sonnet') // { provider: 'anthropic', modelId: 'claude-3-sonnet', original: 'anthropic/claude-3-sonnet' }
* ```
*/
export function parseModel(model: ModelId): ModelConfig {
// Check aliases first
const aliased = MODEL_ALIASES[model];
const modelString = aliased || model;
if (modelString.includes('/')) {
const parts = modelString.split('/');
return {
provider: parts[0].toLowerCase(),
modelId: parts.slice(1).join('/'),
original: model,
};
}
// Infer provider from model name
let provider = 'openai';
if (modelString.startsWith('claude')) provider = 'anthropic';
else if (modelString.startsWith('gemini')) provider = 'google';
else if (modelString.startsWith('deepseek')) provider = 'deepseek';
else if (modelString.startsWith('llama')) provider = 'groq';
return {
provider,
modelId: modelString,
original: model,
};
}
/**
* Get a model instance from a model string.
*
* @example
* ```typescript
* const model = await getModel('gpt-4o');
* const result = await generateText({ model, prompt: 'Hello' });
* ```
*/
export async function getModel(model: ModelId): Promise<any> {
const config = parseModel(model);
let providerModule: any;
try {
switch (config.provider) {
case 'openai':
providerModule = await import('@ai-sdk/openai');
return providerModule.openai(config.modelId);
case 'anthropic':
providerModule = await import('@ai-sdk/anthropic');
return providerModule.anthropic(config.modelId);
case 'google':
providerModule = await import('@ai-sdk/google');
return providerModule.google(config.modelId);
case 'groq':
// @ts-ignore - Optional dependency
providerModule = await import('@ai-sdk/groq');
return providerModule.groq(config.modelId);
case 'deepseek':
// @ts-ignore - Optional dependency
providerModule = await import('@ai-sdk/deepseek');
return providerModule.deepseek(config.modelId);
default:
// Try OpenAI-compatible
providerModule = await import('@ai-sdk/openai');
return providerModule.openai(config.modelId);
}
} catch (error: any) {
throw new Error(
`Failed to load provider '${config.provider}': ${error.message}. ` +
`Install with: npm install @ai-sdk/${config.provider}`
);
}
}
/**
* Create a model with custom configuration.
*
* @example
* ```typescript
* const model = await createModel('gpt-4o', {
* baseURL: 'https://custom-endpoint.com/v1',
* apiKey: 'custom-key'
* });
* ```
*/
export async function createModel(
model: ModelId,
options?: {
baseURL?: string;
apiKey?: string;
headers?: Record<string, string>;
}
): Promise<any> {
const config = parseModel(model);
let providerModule: any;
try {
switch (config.provider) {
case 'openai':
providerModule = await import('@ai-sdk/openai');
const openaiProvider = providerModule.createOpenAI({
baseURL: options?.baseURL,
apiKey: options?.apiKey,
headers: options?.headers,
});
return openaiProvider(config.modelId);
case 'anthropic':
providerModule = await import('@ai-sdk/anthropic');
const anthropicProvider = providerModule.createAnthropic({
baseURL: options?.baseURL,
apiKey: options?.apiKey,
headers: options?.headers,
});
return anthropicProvider(config.modelId);
case 'google':
providerModule = await import('@ai-sdk/google');
const googleProvider = providerModule.createGoogleGenerativeAI({
baseURL: options?.baseURL,
apiKey: options?.apiKey,
headers: options?.headers,
});
return googleProvider(config.modelId);
default:
providerModule = await import('@ai-sdk/openai');
const defaultProvider = providerModule.createOpenAI({
baseURL: options?.baseURL,
apiKey: options?.apiKey,
headers: options?.headers,
});
return defaultProvider(config.modelId);
}
} catch (error: any) {
throw new Error(
`Failed to create model '${model}': ${error.message}`
);
}
}
/**
* List all available model aliases.
*/
export function listModelAliases(): string[] {
return Object.keys(MODEL_ALIASES);
}
/**
* Check if a model alias exists.
*/
export function hasModelAlias(alias: string): boolean {
return alias in MODEL_ALIASES;
}
/**
* Resolve a model alias to its full provider/model format.
*/
export function resolveModelAlias(alias: string): string {
return MODEL_ALIASES[alias] || alias;
}
+291
View File
@@ -0,0 +1,291 @@
/**
* Multimodal - AI SDK Wrapper
*
* Provides utilities for multimodal inputs (images, files, PDFs).
*/
import * as fs from 'fs';
import * as path from 'path';
export type InputPart = TextPart | ImagePart | FilePart | PdfPart;
export interface TextPart {
type: 'text';
text: string;
}
export interface ImagePart {
type: 'image';
/** Image data as URL, base64 string, or Uint8Array */
image: string | Uint8Array | URL;
/** MIME type (auto-detected if not provided) */
mimeType?: string;
}
export interface FilePart {
type: 'file';
/** File data as URL, base64 string, or Uint8Array */
data: string | Uint8Array | URL;
/** MIME type (required) */
mimeType: string;
/** File name (optional) */
name?: string;
}
export interface PdfPart {
type: 'pdf';
/** PDF data as URL, base64 string, or Uint8Array */
data: string | Uint8Array | URL;
/** Page range to extract (optional) */
pages?: { start?: number; end?: number };
/** Extract text only (default: false) */
textOnly?: boolean;
}
/**
* Create a text part.
*/
export function createTextPart(text: string): TextPart {
return { type: 'text', text };
}
/**
* Create an image part from various sources.
*
* @example From URL
* ```typescript
* const imagePart = createImagePart('https://example.com/image.png');
* ```
*
* @example From base64
* ```typescript
* const imagePart = createImagePart('data:image/png;base64,...');
* ```
*
* @example From file path
* ```typescript
* const imagePart = await createImagePart('./image.png');
* ```
*/
export function createImagePart(
source: string | Uint8Array | URL,
mimeType?: string
): ImagePart {
// If it's a file path, read it
if (typeof source === 'string' && !source.startsWith('http') && !source.startsWith('data:')) {
if (fs.existsSync(source)) {
const buffer = fs.readFileSync(source);
const detectedMime = mimeType || detectImageMimeType(source);
return {
type: 'image',
image: buffer,
mimeType: detectedMime,
};
}
}
return {
type: 'image',
image: source,
mimeType,
};
}
/**
* Create a file part from various sources.
*
* @example
* ```typescript
* const filePart = createFilePart('./document.txt', 'text/plain');
* ```
*/
export function createFilePart(
source: string | Uint8Array | URL,
mimeType: string,
name?: string
): FilePart {
// If it's a file path, read it
if (typeof source === 'string' && !source.startsWith('http') && !source.startsWith('data:')) {
if (fs.existsSync(source)) {
const buffer = fs.readFileSync(source);
return {
type: 'file',
data: buffer,
mimeType,
name: name || path.basename(source),
};
}
}
return {
type: 'file',
data: source,
mimeType,
name,
};
}
/**
* Create a PDF part from various sources.
*
* @example
* ```typescript
* const pdfPart = createPdfPart('./document.pdf');
* ```
*
* @example With page range
* ```typescript
* const pdfPart = createPdfPart('./document.pdf', { pages: { start: 1, end: 5 } });
* ```
*/
export function createPdfPart(
source: string | Uint8Array | URL,
options?: { pages?: { start?: number; end?: number }; textOnly?: boolean }
): PdfPart {
// If it's a file path, read it
if (typeof source === 'string' && !source.startsWith('http') && !source.startsWith('data:')) {
if (fs.existsSync(source)) {
const buffer = fs.readFileSync(source);
return {
type: 'pdf',
data: buffer,
pages: options?.pages,
textOnly: options?.textOnly,
};
}
}
return {
type: 'pdf',
data: source,
pages: options?.pages,
textOnly: options?.textOnly,
};
}
/**
* Convert input parts to AI SDK message content format.
*/
export function toMessageContent(parts: InputPart[]): any[] {
return parts.map(part => {
switch (part.type) {
case 'text':
return { type: 'text', text: part.text };
case 'image':
return {
type: 'image',
image: part.image,
mimeType: part.mimeType,
};
case 'file':
return {
type: 'file',
data: part.data,
mimeType: part.mimeType,
};
case 'pdf':
// PDFs are treated as files with application/pdf mime type
return {
type: 'file',
data: part.data,
mimeType: 'application/pdf',
};
default:
return part;
}
});
}
/**
* Create a multimodal message with text and images.
*
* @example
* ```typescript
* const message = createMultimodalMessage(
* 'What is in this image?',
* ['https://example.com/image.png']
* );
* ```
*/
export function createMultimodalMessage(
text: string,
images: (string | Uint8Array | URL)[],
role: 'user' | 'assistant' = 'user'
): { role: string; content: any[] } {
const content: any[] = [];
// Add images first
for (const image of images) {
content.push(createImagePart(image));
}
// Add text
content.push(createTextPart(text));
return {
role,
content: toMessageContent(content as InputPart[]),
};
}
/**
* Detect image MIME type from file extension.
*/
function detectImageMimeType(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
const mimeTypes: Record<string, string> = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.svg': 'image/svg+xml',
'.bmp': 'image/bmp',
'.ico': 'image/x-icon',
};
return mimeTypes[ext] || 'image/png';
}
/**
* Convert a base64 string to Uint8Array.
*/
export function base64ToUint8Array(base64: string): Uint8Array {
// Handle data URLs
const base64Data = base64.includes(',') ? base64.split(',')[1] : base64;
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
/**
* Convert Uint8Array to base64 string.
*/
export function uint8ArrayToBase64(bytes: Uint8Array): string {
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
/**
* Check if a string is a valid URL.
*/
export function isUrl(str: string): boolean {
try {
new URL(str);
return true;
} catch {
return false;
}
}
/**
* Check if a string is a data URL.
*/
export function isDataUrl(str: string): boolean {
return str.startsWith('data:');
}
+265
View File
@@ -0,0 +1,265 @@
/**
* Next.js Integration - AI SDK Wrapper
*
* Provides route handlers and React hooks for Next.js applications.
*/
export interface RouteHandlerConfig {
/** Agent or handler function */
handler: (input: any) => Promise<any>;
/** Enable streaming (default: true) */
streaming?: boolean;
/** Maximum duration in seconds */
maxDuration?: number;
/** CORS configuration */
cors?: boolean | {
origin?: string | string[];
methods?: string[];
};
}
/**
* Create a Next.js App Router route handler.
*
* @example app/api/chat/route.ts
* ```typescript
* import { createRouteHandler } from 'praisonai/ai';
* import { streamText } from 'praisonai/ai';
*
* export const POST = createRouteHandler({
* handler: async (input) => {
* return await streamText({
* model: 'gpt-4o',
* messages: input.messages
* });
* }
* });
* ```
*
* @example With Agent
* ```typescript
* import { createRouteHandler } from 'praisonai/ai';
* import { Agent } from 'praisonai';
*
* const agent = new Agent({ instructions: 'You are helpful' });
*
* export const POST = createRouteHandler({
* handler: async (input) => {
* return await agent.chat(input.message);
* }
* });
* ```
*/
export function createRouteHandler(config: RouteHandlerConfig) {
return async (request: Request): Promise<Response> => {
try {
// Parse request body
const body = await request.json();
// Execute handler
const result = await config.handler(body);
// Handle streaming response
if (config.streaming !== false && result.textStream) {
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
try {
for await (const chunk of result.textStream) {
const data = JSON.stringify({ text: chunk });
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
}
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
} catch (error) {
controller.error(error);
} finally {
controller.close();
}
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
...getCorsHeaders(config.cors),
},
});
}
// Handle toDataStreamResponse if available (AI SDK)
if (result.toDataStreamResponse) {
return result.toDataStreamResponse();
}
// Handle regular response
const responseData = typeof result === 'string'
? { text: result }
: result.text
? { text: result.text, usage: result.usage }
: result;
return new Response(JSON.stringify(responseData), {
headers: {
'Content-Type': 'application/json',
...getCorsHeaders(config.cors),
},
});
} catch (error: any) {
console.error('Route handler error:', error);
return new Response(
JSON.stringify({ error: error.message || 'Internal server error' }),
{
status: 500,
headers: {
'Content-Type': 'application/json',
...getCorsHeaders(config.cors),
},
}
);
}
};
}
/**
* Create a Next.js Pages Router API handler.
*
* @example pages/api/chat.ts
* ```typescript
* import { createPagesHandler } from 'praisonai/ai';
* import { streamText } from 'praisonai/ai';
*
* export default createPagesHandler({
* handler: async (input) => {
* return await streamText({
* model: 'gpt-4o',
* messages: input.messages
* });
* }
* });
* ```
*/
export function createPagesHandler(config: RouteHandlerConfig) {
return async (req: any, res: any): Promise<void> => {
try {
// Set CORS headers
if (config.cors) {
const headers = getCorsHeaders(config.cors);
for (const [key, value] of Object.entries(headers)) {
res.setHeader(key, value);
}
}
// Handle preflight
if (req.method === 'OPTIONS') {
res.status(204).end();
return;
}
// Execute handler
const result = await config.handler(req.body);
// Handle streaming response
if (config.streaming !== false && result.textStream) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
for await (const chunk of result.textStream) {
res.write(`data: ${JSON.stringify({ text: chunk })}\n\n`);
}
res.write('data: [DONE]\n\n');
res.end();
return;
}
// Handle regular response
const responseData = typeof result === 'string'
? { text: result }
: result.text
? { text: result.text, usage: result.usage }
: result;
res.status(200).json(responseData);
} catch (error: any) {
console.error('Pages handler error:', error);
res.status(500).json({ error: error.message || 'Internal server error' });
}
};
}
/**
* Get CORS headers based on configuration.
*/
function getCorsHeaders(cors?: boolean | { origin?: string | string[]; methods?: string[] }): Record<string, string> {
if (!cors) return {};
const config = typeof cors === 'boolean'
? { origin: '*', methods: ['GET', 'POST', 'OPTIONS'] }
: cors;
const origin = Array.isArray(config.origin)
? config.origin.join(', ')
: (config.origin || '*');
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': (config.methods || ['GET', 'POST', 'OPTIONS']).join(', '),
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
}
/**
* Configuration for useChat hook wrapper.
*/
export interface UseChatConfig {
/** API endpoint */
api?: string;
/** Initial messages */
initialMessages?: Array<{ role: string; content: string }>;
/** On error callback */
onError?: (error: Error) => void;
/** On finish callback */
onFinish?: (message: any) => void;
/** Custom headers */
headers?: Record<string, string>;
/** Custom body */
body?: Record<string, any>;
}
/**
* Note: For React hooks (useChat, useCompletion, useObject),
* use the AI SDK React package directly:
*
* ```typescript
* import { useChat } from 'ai/react';
*
* function ChatComponent() {
* const { messages, input, handleInputChange, handleSubmit } = useChat({
* api: '/api/chat'
* });
*
* return (
* <form onSubmit={handleSubmit}>
* {messages.map(m => <div key={m.id}>{m.content}</div>)}
* <input value={input} onChange={handleInputChange} />
* </form>
* );
* }
* ```
*
* The praisonai package focuses on server-side functionality.
* For client-side React hooks, install and use 'ai/react' directly.
*/
/**
* Export runtime configuration for Next.js edge runtime.
*/
export const runtime = 'edge';
/**
* Export max duration configuration.
*/
export const maxDuration = 30;
+372
View File
@@ -0,0 +1,372 @@
/**
* Server Adapters - AI SDK Wrapper
*
* Provides server adapters for HTTP, Express, Hono, Fastify, and Nest.js.
*/
import type { IncomingMessage, ServerResponse } from 'http';
export interface ServerHandler {
/** Handle a request */
handle(req: any, res: any): Promise<void>;
}
export interface ServerHandlerConfig {
/** Agent or generateText/streamText function */
handler: (input: any) => Promise<any>;
/** Enable streaming (default: true) */
streaming?: boolean;
/** CORS headers */
cors?: boolean | CorsConfig;
/** Request body parser */
bodyParser?: (req: any) => Promise<any>;
/** Response formatter */
responseFormatter?: (result: any) => any;
/** Error handler */
onError?: (error: Error, req: any, res: any) => void;
}
export interface CorsConfig {
origin?: string | string[];
methods?: string[];
headers?: string[];
credentials?: boolean;
}
/**
* Create an HTTP server handler.
*
* @example
* ```typescript
* import { createServer } from 'http';
* import { createHttpHandler } from 'praisonai/ai';
*
* const handler = createHttpHandler({
* handler: async (input) => {
* return await generateText({ model: 'gpt-4o', prompt: input.prompt });
* }
* });
*
* createServer(handler.handle).listen(3000);
* ```
*/
export function createHttpHandler(config: ServerHandlerConfig): ServerHandler {
return {
handle: async (req: IncomingMessage, res: ServerResponse) => {
try {
// Set CORS headers
if (config.cors) {
setCorsHeaders(res, config.cors);
}
// Handle preflight
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
// Parse body
const body = config.bodyParser
? await config.bodyParser(req)
: await parseJsonBody(req);
// Execute handler
const result = await config.handler(body);
// Format response
const response = config.responseFormatter
? config.responseFormatter(result)
: result;
// Send response
if (config.streaming && result.textStream) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
for await (const chunk of result.textStream) {
res.write(`data: ${JSON.stringify({ text: chunk })}\n\n`);
}
res.write('data: [DONE]\n\n');
res.end();
} else {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(response));
}
} catch (error: any) {
if (config.onError) {
config.onError(error, req, res);
} else {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
}
},
};
}
/**
* Create an Express handler.
*
* @example
* ```typescript
* import express from 'express';
* import { createExpressHandler } from 'praisonai/ai';
*
* const app = express();
* app.use(express.json());
*
* app.post('/api/chat', createExpressHandler({
* handler: async (input) => {
* return await streamText({ model: 'gpt-4o', messages: input.messages });
* }
* }));
* ```
*/
export function createExpressHandler(config: ServerHandlerConfig): (req: any, res: any) => Promise<void> {
return async (req: any, res: any) => {
try {
// Set CORS headers
if (config.cors) {
setCorsHeaders(res, config.cors);
}
// Execute handler
const result = await config.handler(req.body);
// Format response
const response = config.responseFormatter
? config.responseFormatter(result)
: result;
// Send response
if (config.streaming && result.textStream) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
for await (const chunk of result.textStream) {
res.write(`data: ${JSON.stringify({ text: chunk })}\n\n`);
}
res.write('data: [DONE]\n\n');
res.end();
} else {
res.json(response);
}
} catch (error: any) {
if (config.onError) {
config.onError(error, req, res);
} else {
res.status(500).json({ error: error.message });
}
}
};
}
/**
* Create a Hono handler.
*
* @example
* ```typescript
* import { Hono } from 'hono';
* import { createHonoHandler } from 'praisonai/ai';
*
* const app = new Hono();
*
* app.post('/api/chat', createHonoHandler({
* handler: async (input) => {
* return await streamText({ model: 'gpt-4o', messages: input.messages });
* }
* }));
* ```
*/
export function createHonoHandler(config: ServerHandlerConfig): (c: any) => Promise<Response> {
return async (c: any) => {
try {
const body = await c.req.json();
const result = await config.handler(body);
const response = config.responseFormatter
? config.responseFormatter(result)
: result;
if (config.streaming && result.textStream) {
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of result.textStream) {
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify({ text: chunk })}\n\n`));
}
controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
return c.json(response);
} catch (error: any) {
return c.json({ error: error.message }, 500);
}
};
}
/**
* Create a Fastify handler.
*
* @example
* ```typescript
* import Fastify from 'fastify';
* import { createFastifyHandler } from 'praisonai/ai';
*
* const fastify = Fastify();
*
* fastify.post('/api/chat', createFastifyHandler({
* handler: async (input) => {
* return await streamText({ model: 'gpt-4o', messages: input.messages });
* }
* }));
* ```
*/
export function createFastifyHandler(config: ServerHandlerConfig): (request: any, reply: any) => Promise<void> {
return async (request: any, reply: any) => {
try {
const result = await config.handler(request.body);
const response = config.responseFormatter
? config.responseFormatter(result)
: result;
if (config.streaming && result.textStream) {
reply.raw.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
for await (const chunk of result.textStream) {
reply.raw.write(`data: ${JSON.stringify({ text: chunk })}\n\n`);
}
reply.raw.write('data: [DONE]\n\n');
reply.raw.end();
} else {
reply.send(response);
}
} catch (error: any) {
if (config.onError) {
config.onError(error, request, reply);
} else {
reply.status(500).send({ error: error.message });
}
}
};
}
/**
* Create a Nest.js controller method decorator config.
*
* @example
* ```typescript
* import { Controller, Post, Body, Res } from '@nestjs/common';
* import { createNestHandler } from 'praisonai/ai';
*
* @Controller('api')
* export class ChatController {
* private handler = createNestHandler({
* handler: async (input) => {
* return await streamText({ model: 'gpt-4o', messages: input.messages });
* }
* });
*
* @Post('chat')
* async chat(@Body() body: any, @Res() res: any) {
* return this.handler(body, res);
* }
* }
* ```
*/
export function createNestHandler(config: ServerHandlerConfig): (body: any, res: any) => Promise<void> {
return async (body: any, res: any) => {
try {
const result = await config.handler(body);
const response = config.responseFormatter
? config.responseFormatter(result)
: result;
if (config.streaming && result.textStream) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
for await (const chunk of result.textStream) {
res.write(`data: ${JSON.stringify({ text: chunk })}\n\n`);
}
res.write('data: [DONE]\n\n');
res.end();
} else {
res.json(response);
}
} catch (error: any) {
if (config.onError) {
config.onError(error, body, res);
} else {
res.status(500).json({ error: error.message });
}
}
};
}
/**
* Parse JSON body from HTTP request.
*/
async function parseJsonBody(req: IncomingMessage): Promise<any> {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
resolve(body ? JSON.parse(body) : {});
} catch (e) {
reject(new Error('Invalid JSON body'));
}
});
req.on('error', reject);
});
}
/**
* Set CORS headers on response.
*/
function setCorsHeaders(res: any, cors: boolean | CorsConfig): void {
const config: CorsConfig = typeof cors === 'boolean'
? { origin: '*', methods: ['GET', 'POST', 'OPTIONS'], headers: ['Content-Type'] }
: cors;
const origin = Array.isArray(config.origin) ? config.origin.join(', ') : (config.origin || '*');
if (res.setHeader) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', (config.methods || ['GET', 'POST', 'OPTIONS']).join(', '));
res.setHeader('Access-Control-Allow-Headers', (config.headers || ['Content-Type']).join(', '));
if (config.credentials) {
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
} else if (res.set) {
res.set('Access-Control-Allow-Origin', origin);
res.set('Access-Control-Allow-Methods', (config.methods || ['GET', 'POST', 'OPTIONS']).join(', '));
res.set('Access-Control-Allow-Headers', (config.headers || ['Content-Type']).join(', '));
if (config.credentials) {
res.set('Access-Control-Allow-Credentials', 'true');
}
}
}
+355
View File
@@ -0,0 +1,355 @@
/**
* Speech & Transcription - AI SDK v6 Compatible
*
* Provides speech generation and transcription utilities.
*/
// ============================================================================
// Types
// ============================================================================
export interface GenerateSpeechOptions {
/** Model to use (e.g., 'openai/tts-1', 'elevenlabs/eleven_multilingual_v2') */
model: string;
/** Text to convert to speech */
text: string;
/** Voice to use */
voice?: string;
/** Speed multiplier (0.25 to 4.0) */
speed?: number;
/** Output format */
format?: 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm';
/** Additional provider options */
providerOptions?: Record<string, unknown>;
}
export interface GenerateSpeechResult {
/** Audio data as Uint8Array */
audio: Uint8Array;
/** Audio format */
format: string;
/** Duration in seconds (if available) */
durationSeconds?: number;
/** Provider metadata */
providerMetadata?: Record<string, unknown>;
}
export interface TranscribeOptions {
/** Model to use (e.g., 'openai/whisper-1') */
model: string;
/** Audio data - can be Uint8Array, ArrayBuffer, Buffer, base64 string, or URL */
audio: Uint8Array | ArrayBuffer | Buffer | string;
/** Language hint (ISO 639-1 code) */
language?: string;
/** Prompt to guide transcription */
prompt?: string;
/** Temperature for sampling */
temperature?: number;
/** Output format */
format?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt';
/** Additional provider options */
providerOptions?: Record<string, unknown>;
}
export interface TranscribeResult {
/** Transcribed text */
text: string;
/** Detected language */
language?: string;
/** Duration in seconds */
durationSeconds?: number;
/** Segments with timestamps */
segments?: TranscriptionSegment[];
/** Provider metadata */
providerMetadata?: Record<string, unknown>;
}
export interface TranscriptionSegment {
/** Segment ID */
id: number;
/** Start time in seconds */
start: number;
/** End time in seconds */
end: number;
/** Segment text */
text: string;
/** Confidence score */
confidence?: number;
}
// ============================================================================
// Speech Generation
// ============================================================================
/**
* Generate speech from text.
*
* @example
* ```typescript
* const result = await generateSpeech({
* model: 'openai/tts-1',
* text: 'Hello, world!',
* voice: 'alloy'
* });
*
* // Save to file
* await fs.writeFile('speech.mp3', result.audio);
* ```
*/
export async function generateSpeech(options: GenerateSpeechOptions): Promise<GenerateSpeechResult> {
const [provider, model] = parseModel(options.model);
switch (provider) {
case 'openai':
return generateSpeechOpenAI(model, options);
case 'elevenlabs':
return generateSpeechElevenLabs(model, options);
default:
// Try AI SDK
return generateSpeechAISDK(options);
}
}
async function generateSpeechOpenAI(model: string, options: GenerateSpeechOptions): Promise<GenerateSpeechResult> {
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error('OPENAI_API_KEY environment variable is required for OpenAI speech generation');
}
const response = await fetch('https://api.openai.com/v1/audio/speech', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: model || 'tts-1',
input: options.text,
voice: options.voice || 'alloy',
speed: options.speed,
response_format: options.format || 'mp3',
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`OpenAI speech generation failed: ${response.status} - ${error}`);
}
const arrayBuffer = await response.arrayBuffer();
return {
audio: new Uint8Array(arrayBuffer),
format: options.format || 'mp3',
};
}
async function generateSpeechElevenLabs(model: string, options: GenerateSpeechOptions): Promise<GenerateSpeechResult> {
const apiKey = process.env.ELEVENLABS_API_KEY;
if (!apiKey) {
throw new Error('ELEVENLABS_API_KEY environment variable is required for ElevenLabs speech generation');
}
const voiceId = options.voice || '21m00Tcm4TlvDq8ikWAM'; // Default: Rachel
const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
method: 'POST',
headers: {
'xi-api-key': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: options.text,
model_id: model || 'eleven_multilingual_v2',
voice_settings: {
stability: 0.5,
similarity_boost: 0.75,
},
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`ElevenLabs speech generation failed: ${response.status} - ${error}`);
}
const arrayBuffer = await response.arrayBuffer();
return {
audio: new Uint8Array(arrayBuffer),
format: 'mp3',
};
}
async function generateSpeechAISDK(options: GenerateSpeechOptions): Promise<GenerateSpeechResult> {
try {
const ai = await import('ai');
if ('generateSpeech' in ai) {
const result = await (ai as any).generateSpeech(options);
return {
audio: result.audio,
format: options.format || 'mp3',
durationSeconds: result.durationSeconds,
providerMetadata: result.providerMetadata,
};
}
} catch {
// AI SDK not available or doesn't support speech
}
throw new Error(`Speech generation not supported for model: ${options.model}`);
}
// ============================================================================
// Transcription
// ============================================================================
/**
* Transcribe audio to text.
*
* @example
* ```typescript
* const result = await transcribe({
* model: 'openai/whisper-1',
* audio: await fs.readFile('audio.mp3')
* });
*
* console.log(result.text);
* ```
*/
export async function transcribe(options: TranscribeOptions): Promise<TranscribeResult> {
const [provider, model] = parseModel(options.model);
switch (provider) {
case 'openai':
return transcribeOpenAI(model, options);
default:
// Try AI SDK
return transcribeAISDK(options);
}
}
async function transcribeOpenAI(model: string, options: TranscribeOptions): Promise<TranscribeResult> {
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error('OPENAI_API_KEY environment variable is required for OpenAI transcription');
}
// Convert audio to appropriate format
let audioData: Uint8Array;
if (typeof options.audio === 'string') {
if (options.audio.startsWith('http')) {
const response = await fetch(options.audio);
audioData = new Uint8Array(await response.arrayBuffer());
} else {
// Assume base64
audioData = Uint8Array.from(atob(options.audio), c => c.charCodeAt(0));
}
} else if (options.audio instanceof ArrayBuffer) {
audioData = new Uint8Array(options.audio);
} else {
audioData = options.audio as Uint8Array;
}
const formData = new FormData();
formData.append('file', new Blob([audioData.buffer as ArrayBuffer]), 'audio.mp3');
formData.append('model', model || 'whisper-1');
if (options.language) formData.append('language', options.language);
if (options.prompt) formData.append('prompt', options.prompt);
if (options.temperature) formData.append('temperature', String(options.temperature));
formData.append('response_format', options.format === 'verbose_json' ? 'verbose_json' : 'json');
const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
},
body: formData,
});
if (!response.ok) {
const error = await response.text();
throw new Error(`OpenAI transcription failed: ${response.status} - ${error}`);
}
const data = await response.json();
if (options.format === 'verbose_json') {
return {
text: data.text,
language: data.language,
durationSeconds: data.duration,
segments: data.segments?.map((s: any, i: number) => ({
id: i,
start: s.start,
end: s.end,
text: s.text,
})),
};
}
return {
text: data.text,
};
}
async function transcribeAISDK(options: TranscribeOptions): Promise<TranscribeResult> {
try {
const ai = await import('ai');
if ('transcribe' in ai) {
const result = await (ai as any).transcribe(options);
return {
text: result.text,
language: result.language,
durationSeconds: result.durationSeconds,
segments: result.segments,
providerMetadata: result.providerMetadata,
};
}
} catch {
// AI SDK not available or doesn't support transcription
}
throw new Error(`Transcription not supported for model: ${options.model}`);
}
// ============================================================================
// Utilities
// ============================================================================
function parseModel(model: string): [string, string] {
if (model.includes('/')) {
const [provider, ...rest] = model.split('/');
return [provider.toLowerCase(), rest.join('/')];
}
// Infer provider from model name
if (model.startsWith('tts-') || model.startsWith('whisper')) {
return ['openai', model];
}
if (model.startsWith('eleven_')) {
return ['elevenlabs', model];
}
return ['openai', model];
}
/**
* Available speech models.
*/
export const SPEECH_MODELS = {
openai: {
'tts-1': { description: 'Standard TTS model', voices: ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'] },
'tts-1-hd': { description: 'High-definition TTS model', voices: ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'] },
},
elevenlabs: {
'eleven_multilingual_v2': { description: 'Multilingual model', voices: [] },
'eleven_turbo_v2': { description: 'Fast model', voices: [] },
},
};
/**
* Available transcription models.
*/
export const TRANSCRIPTION_MODELS = {
openai: {
'whisper-1': { description: 'Whisper large-v2 model', languages: 'auto-detect' },
},
};
+382
View File
@@ -0,0 +1,382 @@
/**
* Telemetry - AI SDK v6 Compatible Telemetry
*
* Provides telemetry settings compatible with AI SDK v6's experimental_telemetry.
* Optionally bridges to OpenTelemetry for production observability.
*/
// ============================================================================
// Types
// ============================================================================
/**
* Telemetry settings compatible with AI SDK v6.
*/
export interface TelemetrySettings {
/** Enable telemetry (default: false) */
isEnabled?: boolean;
/** Record input data (default: true when enabled) */
recordInputs?: boolean;
/** Record output data (default: true when enabled) */
recordOutputs?: boolean;
/** Function identifier for grouping telemetry */
functionId?: string;
/** Additional metadata */
metadata?: Record<string, unknown>;
/** Custom tracer (OpenTelemetry compatible) */
tracer?: Tracer;
}
/**
* OpenTelemetry-compatible tracer interface.
*/
export interface Tracer {
startSpan(name: string, options?: SpanOptions): Span;
startActiveSpan<T>(name: string, fn: (span: Span) => T): T;
startActiveSpan<T>(name: string, options: SpanOptions, fn: (span: Span) => T): T;
}
/**
* OpenTelemetry-compatible span interface.
*/
export interface Span {
setAttribute(key: string, value: string | number | boolean): this;
setAttributes(attributes: Record<string, string | number | boolean>): this;
addEvent(name: string, attributes?: Record<string, unknown>): this;
setStatus(status: SpanStatus): this;
end(): void;
recordException(exception: Error): void;
isRecording(): boolean;
}
export interface SpanOptions {
attributes?: Record<string, string | number | boolean>;
kind?: SpanKind;
}
export type SpanKind = 'internal' | 'server' | 'client' | 'producer' | 'consumer';
export interface SpanStatus {
code: 'ok' | 'error' | 'unset';
message?: string;
}
// ============================================================================
// Global State
// ============================================================================
let globalTelemetrySettings: TelemetrySettings = {
isEnabled: false,
recordInputs: true,
recordOutputs: true,
};
let otelTracer: Tracer | null = null;
// ============================================================================
// Configuration Functions
// ============================================================================
/**
* Configure global telemetry settings.
*
* @example
* ```typescript
* configureTelemetry({
* isEnabled: true,
* functionId: 'my-app',
* metadata: { version: '1.0.0' }
* });
* ```
*/
export function configureTelemetry(settings: TelemetrySettings): void {
globalTelemetrySettings = {
...globalTelemetrySettings,
...settings,
};
}
/**
* Get current telemetry settings.
*/
export function getTelemetrySettings(): TelemetrySettings {
return { ...globalTelemetrySettings };
}
/**
* Enable telemetry globally.
*/
export function enableTelemetry(options?: Omit<TelemetrySettings, 'isEnabled'>): void {
globalTelemetrySettings = {
...globalTelemetrySettings,
...options,
isEnabled: true,
};
}
/**
* Disable telemetry globally.
*/
export function disableTelemetry(): void {
globalTelemetrySettings.isEnabled = false;
}
/**
* Check if telemetry is enabled.
*/
export function isTelemetryEnabled(): boolean {
return globalTelemetrySettings.isEnabled ?? false;
}
// ============================================================================
// OpenTelemetry Integration
// ============================================================================
/**
* Initialize OpenTelemetry integration.
*
* @example
* ```typescript
* import { trace } from '@opentelemetry/api';
*
* initOpenTelemetry({
* tracer: trace.getTracer('praisonai'),
* serviceName: 'my-ai-app'
* });
* ```
*/
export async function initOpenTelemetry(options?: {
tracer?: Tracer;
serviceName?: string;
endpoint?: string;
}): Promise<void> {
if (options?.tracer) {
otelTracer = options.tracer;
globalTelemetrySettings.tracer = options.tracer;
return;
}
// Try to auto-initialize OpenTelemetry
try {
const otelApi = await import('@opentelemetry/api');
otelTracer = otelApi.trace.getTracer(options?.serviceName || 'praisonai') as unknown as Tracer;
globalTelemetrySettings.tracer = otelTracer || undefined;
} catch {
console.warn(
'⚠️ @opentelemetry/api not installed. Install with:\n' +
' npm install @opentelemetry/api @opentelemetry/sdk-node\n' +
' or: pnpm add @opentelemetry/api @opentelemetry/sdk-node'
);
}
}
/**
* Get the OpenTelemetry tracer.
*/
export function getTracer(): Tracer | null {
return otelTracer;
}
// ============================================================================
// Span Helpers
// ============================================================================
/**
* Create a span for an AI operation.
*
* @example
* ```typescript
* const span = createAISpan('generateText', {
* model: 'gpt-4o',
* provider: 'openai'
* });
*
* try {
* const result = await generateText({ ... });
* span.setAttribute('tokens', result.usage.totalTokens);
* span.setStatus({ code: 'ok' });
* } catch (error) {
* span.recordException(error);
* span.setStatus({ code: 'error', message: error.message });
* } finally {
* span.end();
* }
* ```
*/
export function createAISpan(
name: string,
attributes?: Record<string, string | number | boolean>
): Span {
if (!globalTelemetrySettings.isEnabled || !otelTracer) {
return createNoopSpan();
}
return otelTracer.startSpan(`ai.${name}`, {
attributes: {
'ai.operation': name,
...attributes,
},
kind: 'client',
});
}
/**
* Execute a function within a span.
*/
export async function withSpan<T>(
name: string,
attributes: Record<string, string | number | boolean>,
fn: (span: Span) => Promise<T>
): Promise<T> {
const span = createAISpan(name, attributes);
try {
const result = await fn(span);
span.setStatus({ code: 'ok' });
return result;
} catch (error: any) {
span.recordException(error);
span.setStatus({ code: 'error', message: error.message });
throw error;
} finally {
span.end();
}
}
// ============================================================================
// Noop Span (when telemetry is disabled)
// ============================================================================
function createNoopSpan(): Span {
return {
setAttribute: function() { return this; },
setAttributes: function() { return this; },
addEvent: function() { return this; },
setStatus: function() { return this; },
end: function() {},
recordException: function() {},
isRecording: function() { return false; },
};
}
// ============================================================================
// Telemetry Middleware
// ============================================================================
/**
* Create middleware that adds telemetry to AI SDK calls.
*/
export function createTelemetryMiddleware(settings?: TelemetrySettings): any {
const mergedSettings = {
...globalTelemetrySettings,
...settings,
};
return {
transformParams: async ({ params }: { params: any }) => {
if (!mergedSettings.isEnabled) {
return params;
}
return {
...params,
experimental_telemetry: {
isEnabled: true,
recordInputs: mergedSettings.recordInputs,
recordOutputs: mergedSettings.recordOutputs,
functionId: mergedSettings.functionId,
metadata: mergedSettings.metadata,
tracer: mergedSettings.tracer,
},
};
},
};
}
// ============================================================================
// Event Recording
// ============================================================================
export interface TelemetryEvent {
name: string;
timestamp: number;
attributes: Record<string, unknown>;
spanId?: string;
traceId?: string;
}
const eventBuffer: TelemetryEvent[] = [];
const MAX_BUFFER_SIZE = 1000;
/**
* Record a telemetry event.
*/
export function recordEvent(
name: string,
attributes: Record<string, unknown> = {}
): void {
if (!globalTelemetrySettings.isEnabled) {
return;
}
const event: TelemetryEvent = {
name,
timestamp: Date.now(),
attributes: {
...attributes,
functionId: globalTelemetrySettings.functionId,
...globalTelemetrySettings.metadata,
},
};
eventBuffer.push(event);
// Prevent memory leak
if (eventBuffer.length > MAX_BUFFER_SIZE) {
eventBuffer.shift();
}
}
/**
* Get recorded events.
*/
export function getEvents(): TelemetryEvent[] {
return [...eventBuffer];
}
/**
* Clear recorded events.
*/
export function clearEvents(): void {
eventBuffer.length = 0;
}
// ============================================================================
// AI SDK Telemetry Helpers
// ============================================================================
/**
* Create telemetry settings for AI SDK calls.
*/
export function createTelemetrySettings(options?: {
functionId?: string;
metadata?: Record<string, unknown>;
recordInputs?: boolean;
recordOutputs?: boolean;
}): TelemetrySettings | undefined {
if (!globalTelemetrySettings.isEnabled) {
return undefined;
}
return {
isEnabled: true,
functionId: options?.functionId || globalTelemetrySettings.functionId,
metadata: {
...globalTelemetrySettings.metadata,
...options?.metadata,
},
recordInputs: options?.recordInputs ?? globalTelemetrySettings.recordInputs,
recordOutputs: options?.recordOutputs ?? globalTelemetrySettings.recordOutputs,
tracer: globalTelemetrySettings.tracer,
};
}
+501
View File
@@ -0,0 +1,501 @@
/**
* Tool Approval - Human-in-the-Loop Tool Execution
*
* Provides utilities for requiring human approval before tool execution.
* Compatible with AI SDK v6's needsApproval pattern.
*/
import { EventEmitter } from 'events';
// ============================================================================
// Types
// ============================================================================
/**
* Tool approval configuration.
*/
export interface ToolApprovalConfig {
/** Tool name */
name: string;
/** Tool description */
description?: string;
/** Whether approval is needed - can be boolean or async function */
needsApproval?: boolean | ((input: unknown) => boolean | Promise<boolean>);
/** Timeout for approval in ms (default: 5 minutes) */
approvalTimeout?: number;
/** Auto-approve patterns (regex or function) */
autoApprove?: RegExp | ((input: unknown) => boolean);
/** Auto-deny patterns (regex or function) */
autoDeny?: RegExp | ((input: unknown) => boolean);
}
/**
* Tool approval request.
*/
export interface ToolApprovalRequest {
/** Unique ID for this approval request */
requestId: string;
/** Tool invocation ID */
toolInvocationId: string;
/** Tool name */
toolName: string;
/** Tool input arguments */
input: unknown;
/** Timestamp when request was created */
timestamp: number;
/** Optional context about why approval is needed */
reason?: string;
}
/**
* Tool approval response.
*/
export interface ToolApprovalResponse {
/** Request ID being responded to */
requestId: string;
/** Whether the tool call is approved */
approved: boolean;
/** Optional message from approver */
message?: string;
/** Who approved/denied (for audit) */
approver?: string;
/** Timestamp of response */
timestamp: number;
}
/**
* Approval state for tracking pending approvals.
*/
export type ApprovalState = 'pending' | 'approved' | 'denied' | 'timeout' | 'auto-approved' | 'auto-denied';
/**
* Approval handler function type.
*/
export type ApprovalHandler = (request: ToolApprovalRequest) => Promise<boolean>;
// ============================================================================
// Approval Manager
// ============================================================================
/**
* Manages tool approval requests and responses.
*
* @example Basic usage
* ```typescript
* const manager = new ApprovalManager();
*
* // Register a handler
* manager.onApprovalRequest(async (request) => {
* console.log(`Tool ${request.toolName} wants to run with:`, request.input);
* return await askUser(`Approve ${request.toolName}?`);
* });
*
* // Request approval
* const approved = await manager.requestApproval({
* toolName: 'deleteFile',
* input: { path: '/important/file.txt' }
* });
* ```
*/
export class ApprovalManager extends EventEmitter {
private pendingRequests = new Map<string, {
request: ToolApprovalRequest;
resolve: (approved: boolean) => void;
reject: (error: Error) => void;
timeout?: NodeJS.Timeout;
}>();
private handlers: ApprovalHandler[] = [];
private defaultTimeout: number;
private autoApprovePatterns: Array<{ toolName: string | RegExp; inputPattern?: RegExp | ((input: unknown) => boolean) }> = [];
private autoDenyPatterns: Array<{ toolName: string | RegExp; inputPattern?: RegExp | ((input: unknown) => boolean) }> = [];
constructor(options?: { defaultTimeout?: number }) {
super();
this.defaultTimeout = options?.defaultTimeout ?? 5 * 60 * 1000; // 5 minutes
}
/**
* Register an approval handler.
*/
onApprovalRequest(handler: ApprovalHandler): void {
this.handlers.push(handler);
}
/**
* Add auto-approve pattern.
*/
addAutoApprove(toolName: string | RegExp, inputPattern?: RegExp | ((input: unknown) => boolean)): void {
this.autoApprovePatterns.push({ toolName, inputPattern });
}
/**
* Add auto-deny pattern.
*/
addAutoDeny(toolName: string | RegExp, inputPattern?: RegExp | ((input: unknown) => boolean)): void {
this.autoDenyPatterns.push({ toolName, inputPattern });
}
/**
* Check if a tool call should be auto-approved.
*/
private checkAutoApprove(toolName: string, input: unknown): boolean {
for (const pattern of this.autoApprovePatterns) {
const nameMatches = typeof pattern.toolName === 'string'
? pattern.toolName === toolName
: pattern.toolName.test(toolName);
if (!nameMatches) continue;
if (!pattern.inputPattern) return true;
if (typeof pattern.inputPattern === 'function') {
if (pattern.inputPattern(input)) return true;
} else {
const inputStr = JSON.stringify(input);
if (pattern.inputPattern.test(inputStr)) return true;
}
}
return false;
}
/**
* Check if a tool call should be auto-denied.
*/
private checkAutoDeny(toolName: string, input: unknown): boolean {
for (const pattern of this.autoDenyPatterns) {
const nameMatches = typeof pattern.toolName === 'string'
? pattern.toolName === toolName
: pattern.toolName.test(toolName);
if (!nameMatches) continue;
if (!pattern.inputPattern) return true;
if (typeof pattern.inputPattern === 'function') {
if (pattern.inputPattern(input)) return true;
} else {
const inputStr = JSON.stringify(input);
if (pattern.inputPattern.test(inputStr)) return true;
}
}
return false;
}
/**
* Request approval for a tool call.
*/
async requestApproval(options: {
toolInvocationId?: string;
toolName: string;
input: unknown;
reason?: string;
timeout?: number;
}): Promise<boolean> {
const requestId = crypto.randomUUID();
const toolInvocationId = options.toolInvocationId || crypto.randomUUID();
// Check auto-deny first (safety)
if (this.checkAutoDeny(options.toolName, options.input)) {
this.emit('auto-denied', { toolName: options.toolName, input: options.input });
return false;
}
// Check auto-approve
if (this.checkAutoApprove(options.toolName, options.input)) {
this.emit('auto-approved', { toolName: options.toolName, input: options.input });
return true;
}
const request: ToolApprovalRequest = {
requestId,
toolInvocationId,
toolName: options.toolName,
input: options.input,
timestamp: Date.now(),
reason: options.reason,
};
// If we have handlers, use them
if (this.handlers.length > 0) {
for (const handler of this.handlers) {
try {
const approved = await handler(request);
if (approved) return true;
} catch (error) {
// Handler failed, continue to next
}
}
return false;
}
// Otherwise, emit event and wait for response
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.pendingRequests.delete(requestId);
this.emit('timeout', request);
resolve(false); // Default to deny on timeout
}, options.timeout ?? this.defaultTimeout);
this.pendingRequests.set(requestId, {
request,
resolve,
reject,
timeout,
});
this.emit('approval-request', request);
});
}
/**
* Respond to an approval request.
*/
respond(response: ToolApprovalResponse): void {
const pending = this.pendingRequests.get(response.requestId);
if (!pending) {
throw new Error(`No pending request with ID: ${response.requestId}`);
}
if (pending.timeout) {
clearTimeout(pending.timeout);
}
this.pendingRequests.delete(response.requestId);
this.emit('approval-response', response);
pending.resolve(response.approved);
}
/**
* Get all pending approval requests.
*/
getPendingRequests(): ToolApprovalRequest[] {
return Array.from(this.pendingRequests.values()).map(p => p.request);
}
/**
* Cancel a pending request.
*/
cancel(requestId: string): void {
const pending = this.pendingRequests.get(requestId);
if (pending) {
if (pending.timeout) {
clearTimeout(pending.timeout);
}
this.pendingRequests.delete(requestId);
pending.resolve(false);
}
}
/**
* Cancel all pending requests.
*/
cancelAll(): void {
for (const [requestId] of this.pendingRequests) {
this.cancel(requestId);
}
}
}
// ============================================================================
// Global Instance
// ============================================================================
let globalApprovalManager: ApprovalManager | null = null;
/**
* Get the global approval manager.
*/
export function getApprovalManager(): ApprovalManager {
if (!globalApprovalManager) {
globalApprovalManager = new ApprovalManager();
}
return globalApprovalManager;
}
/**
* Set a custom global approval manager.
*/
export function setApprovalManager(manager: ApprovalManager): void {
globalApprovalManager = manager;
}
// ============================================================================
// Tool Wrapper
// ============================================================================
/**
* Wrap a tool with approval requirement.
*
* @example
* ```typescript
* const deleteFile = withApproval({
* name: 'deleteFile',
* needsApproval: true,
* execute: async (args) => {
* await fs.unlink(args.path);
* return { success: true };
* }
* });
* ```
*/
export function withApproval<TInput, TOutput>(options: {
name: string;
description?: string;
needsApproval?: boolean | ((input: TInput) => boolean | Promise<boolean>);
execute: (input: TInput) => Promise<TOutput>;
onDenied?: (input: TInput) => TOutput | Promise<TOutput>;
approvalManager?: ApprovalManager;
}): (input: TInput) => Promise<TOutput> {
const manager = options.approvalManager || getApprovalManager();
return async (input: TInput): Promise<TOutput> => {
// Check if approval is needed
let needsApproval = false;
if (typeof options.needsApproval === 'function') {
needsApproval = await options.needsApproval(input);
} else {
needsApproval = options.needsApproval ?? false;
}
if (needsApproval) {
const approved = await manager.requestApproval({
toolName: options.name,
input,
reason: options.description,
});
if (!approved) {
if (options.onDenied) {
return options.onDenied(input);
}
throw new ToolApprovalDeniedError(options.name, input);
}
}
return options.execute(input);
};
}
// ============================================================================
// Errors
// ============================================================================
/**
* Error thrown when tool approval is denied.
*/
export class ToolApprovalDeniedError extends Error {
readonly toolName: string;
readonly input: unknown;
constructor(toolName: string, input: unknown) {
super(`Tool approval denied for: ${toolName}`);
this.name = 'ToolApprovalDeniedError';
this.toolName = toolName;
this.input = input;
}
}
/**
* Error thrown when tool approval times out.
*/
export class ToolApprovalTimeoutError extends Error {
readonly toolName: string;
readonly input: unknown;
constructor(toolName: string, input: unknown) {
super(`Tool approval timed out for: ${toolName}`);
this.name = 'ToolApprovalTimeoutError';
this.toolName = toolName;
this.input = input;
}
}
// ============================================================================
// CLI Approval Prompt
// ============================================================================
/**
* Create a CLI-based approval prompt.
*
* @example
* ```typescript
* const manager = getApprovalManager();
* manager.onApprovalRequest(createCLIApprovalPrompt());
* ```
*/
export function createCLIApprovalPrompt(options?: {
/** Custom prompt message */
promptMessage?: (request: ToolApprovalRequest) => string;
/** Input stream (default: process.stdin) */
input?: NodeJS.ReadableStream;
/** Output stream (default: process.stdout) */
output?: NodeJS.WritableStream;
}): ApprovalHandler {
return async (request: ToolApprovalRequest): Promise<boolean> => {
const readline = await import('readline');
const rl = readline.createInterface({
input: options?.input || process.stdin,
output: options?.output || process.stdout,
});
const message = options?.promptMessage
? options.promptMessage(request)
: `\n🔐 Tool "${request.toolName}" requires approval.\n` +
` Input: ${JSON.stringify(request.input, null, 2)}\n` +
(request.reason ? ` Reason: ${request.reason}\n` : '') +
`\nApprove? (y/n): `;
return new Promise((resolve) => {
rl.question(message, (answer) => {
rl.close();
const approved = answer.toLowerCase().startsWith('y');
console.log(approved ? '✅ Approved' : '❌ Denied');
resolve(approved);
});
});
};
}
// ============================================================================
// Dangerous Tool Patterns
// ============================================================================
/**
* Common dangerous patterns that should require approval.
*/
export const DANGEROUS_PATTERNS = {
/** File deletion patterns */
fileDelete: /\b(rm|delete|remove|unlink)\b.*\b(file|dir|folder|path)\b/i,
/** Database destructive patterns */
dbDestructive: /\b(DROP|DELETE|TRUNCATE|ALTER)\b/i,
/** Shell command patterns */
shellDangerous: /\b(rm\s+-rf|sudo|chmod|chown|mkfs|dd\s+if=)\b/i,
/** Network patterns */
networkSensitive: /\b(curl|wget|fetch)\b.*\b(password|token|secret|key)\b/i,
};
/**
* Check if input matches any dangerous pattern.
*/
export function isDangerous(input: unknown): boolean {
const str = typeof input === 'string' ? input : JSON.stringify(input);
return Object.values(DANGEROUS_PATTERNS).some(pattern => pattern.test(str));
}
/**
* Create a needsApproval function that checks for dangerous patterns.
*/
export function createDangerousPatternChecker(
additionalPatterns?: RegExp[]
): (input: unknown) => boolean {
const patterns = [
...Object.values(DANGEROUS_PATTERNS),
...(additionalPatterns || []),
];
return (input: unknown): boolean => {
const str = typeof input === 'string' ? input : JSON.stringify(input);
return patterns.some(pattern => pattern.test(str));
};
}
+122
View File
@@ -0,0 +1,122 @@
/**
* Tools - AI SDK Wrapper
*
* Provides tool definition utilities compatible with AI SDK.
*/
export interface ToolDefinition<TInput = unknown, TOutput = unknown> {
/** Tool description */
description: string;
/** Input parameters schema (Zod or JSON schema) */
parameters: any;
/** Execute function */
execute: ToolExecuteFunction<TInput, TOutput>;
}
export type ToolExecuteFunction<TInput = unknown, TOutput = unknown> = (
args: TInput,
options?: ToolExecutionOptions
) => Promise<TOutput> | TOutput;
export interface ToolExecutionOptions {
/** Tool call ID */
toolCallId?: string;
/** Abort signal */
abortSignal?: AbortSignal;
/** Messages context */
messages?: any[];
}
export type ToolInput<T extends ToolDefinition> = T extends ToolDefinition<infer I, any> ? I : never;
export type ToolOutput<T extends ToolDefinition> = T extends ToolDefinition<any, infer O> ? O : never;
/**
* Define a tool for use with AI SDK.
*
* @example With Zod schema
* ```typescript
* import { z } from 'zod';
*
* const weatherTool = defineTool({
* description: 'Get weather for a city',
* parameters: z.object({
* city: z.string().describe('City name'),
* unit: z.enum(['celsius', 'fahrenheit']).optional()
* }),
* execute: async ({ city, unit }) => {
* return { temperature: 20, unit: unit || 'celsius', city };
* }
* });
* ```
*
* @example With JSON schema
* ```typescript
* const searchTool = defineTool({
* description: 'Search the web',
* parameters: {
* type: 'object',
* properties: {
* query: { type: 'string', description: 'Search query' }
* },
* required: ['query']
* },
* execute: async ({ query }) => {
* return { results: [`Result for: ${query}`] };
* }
* });
* ```
*/
export function defineTool<TInput = unknown, TOutput = unknown>(
definition: ToolDefinition<TInput, TOutput>
): ToolDefinition<TInput, TOutput> {
return definition;
}
/**
* Create a tool set from multiple tool definitions.
*
* @example
* ```typescript
* const tools = createToolSet({
* weather: weatherTool,
* search: searchTool
* });
*
* const result = await generateText({
* model: 'gpt-4o',
* prompt: 'What is the weather in Paris?',
* tools
* });
* ```
*/
export function createToolSet<T extends Record<string, ToolDefinition>>(
tools: T
): T {
return tools;
}
/**
* Convert a simple function to a tool definition.
*
* @example
* ```typescript
* const addTool = functionToTool(
* 'add',
* 'Add two numbers',
* z.object({ a: z.number(), b: z.number() }),
* ({ a, b }) => a + b
* );
* ```
*/
export function functionToTool<TInput = unknown, TOutput = unknown>(
name: string,
description: string,
parameters: any,
execute: ToolExecuteFunction<TInput, TOutput>
): ToolDefinition<TInput, TOutput> {
return {
description,
parameters,
execute,
};
}
+56
View File
@@ -0,0 +1,56 @@
/**
* Shared Types for AI Module
*/
export interface Message {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string | ContentPart[];
name?: string;
toolCallId?: string;
toolCalls?: ToolCallPart[];
}
export type ContentPart = TextPart | ImagePart | FilePart | ToolCallPart | ToolResultPart;
export interface TextPart {
type: 'text';
text: string;
}
export interface ImagePart {
type: 'image';
image: string | Uint8Array | URL;
mimeType?: string;
}
export interface FilePart {
type: 'file';
data: string | Uint8Array | URL;
mimeType: string;
name?: string;
}
export interface ToolCallPart {
type: 'tool-call';
toolCallId: string;
toolName: string;
args: Record<string, unknown>;
}
export interface ToolResultPart {
type: 'tool-result';
toolCallId: string;
toolName: string;
result: unknown;
isError?: boolean;
}
export interface TokenUsage {
promptTokens: number;
completionTokens: number;
totalTokens: number;
}
export interface ProviderMetadata {
[key: string]: unknown;
}
+501
View File
@@ -0,0 +1,501 @@
/**
* UIMessage - AI SDK v6 Compatible UI Message Types
*
* Provides types and utilities for working with UI messages in chat applications.
* Compatible with AI SDK v6's UIMessage format for seamless integration.
*/
// ============================================================================
// Core Types
// ============================================================================
/**
* UI Message - The main message type for chat UIs.
* Compatible with AI SDK v6's UIMessage format.
*/
export interface UIMessage<
METADATA = unknown,
DATA_PARTS extends UIDataTypes = UIDataTypes,
TOOLS extends UITools = UITools,
> {
/** Unique identifier for the message */
id: string;
/** Role of the message sender */
role: 'system' | 'user' | 'assistant';
/** Optional metadata */
metadata?: METADATA;
/** Message parts for rendering */
parts: Array<UIMessagePart<DATA_PARTS, TOOLS>>;
}
/** Data types that can be used in UI message data parts */
export type UIDataTypes = Record<string, unknown>;
/** UI Tool definition */
export type UITool = {
input: unknown;
output: unknown | undefined;
};
/** UI Tools map */
export type UITools = Record<string, UITool>;
/** All possible UI message part types */
export type UIMessagePart<
DATA_TYPES extends UIDataTypes = UIDataTypes,
TOOLS extends UITools = UITools,
> =
| TextUIPart
| ReasoningUIPart
| ToolUIPart<TOOLS>
| SourceUrlUIPart
| SourceDocumentUIPart
| FileUIPart
| DataUIPart<DATA_TYPES>
| StepStartUIPart;
// ============================================================================
// Part Types
// ============================================================================
/** Text part of a message */
export interface TextUIPart {
type: 'text';
text: string;
state?: 'streaming' | 'done';
providerMetadata?: Record<string, unknown>;
}
/** Reasoning part of a message */
export interface ReasoningUIPart {
type: 'reasoning';
text: string;
state?: 'streaming' | 'done';
providerMetadata?: Record<string, unknown>;
}
/** Tool invocation part */
export interface ToolUIPart<TOOLS extends UITools = UITools> {
type: 'tool';
toolInvocationId: string;
toolName: keyof TOOLS & string;
state: 'input-streaming' | 'input-available' | 'output-streaming' | 'output-available' | 'error';
input?: TOOLS[keyof TOOLS]['input'];
output?: TOOLS[keyof TOOLS]['output'];
error?: string;
/** Whether this tool needs approval before execution */
needsApproval?: boolean;
/** Approval status */
approvalStatus?: 'pending' | 'approved' | 'denied';
}
/** Source URL part */
export interface SourceUrlUIPart {
type: 'source-url';
sourceId: string;
url: string;
title?: string;
providerMetadata?: Record<string, unknown>;
}
/** Source document part */
export interface SourceDocumentUIPart {
type: 'source-document';
sourceId: string;
mediaType: string;
title?: string;
providerMetadata?: Record<string, unknown>;
}
/** File part */
export interface FileUIPart {
type: 'file';
mediaType: string;
url?: string;
data?: string;
filename?: string;
}
/** Data part for custom data */
export interface DataUIPart<DATA_TYPES extends UIDataTypes = UIDataTypes> {
type: 'data';
dataType: keyof DATA_TYPES & string;
data: DATA_TYPES[keyof DATA_TYPES];
}
/** Step start marker */
export interface StepStartUIPart {
type: 'step-start';
stepNumber: number;
}
// ============================================================================
// Model Message Types (for conversion)
// ============================================================================
export interface ModelMessage {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string | ModelMessageContent[];
name?: string;
toolCallId?: string;
toolCalls?: ToolCall[];
}
export type ModelMessageContent =
| { type: 'text'; text: string }
| { type: 'image'; image: string | Uint8Array; mimeType?: string }
| { type: 'file'; data: string | Uint8Array; mimeType: string }
| { type: 'tool-call'; toolCallId: string; toolName: string; args: unknown }
| { type: 'tool-result'; toolCallId: string; toolName: string; result: unknown; isError?: boolean };
export interface ToolCall {
id: string;
type: 'function';
function: {
name: string;
arguments: string;
};
}
// ============================================================================
// Conversion Functions
// ============================================================================
/**
* Convert UI messages to model messages for AI SDK functions.
*
* @example
* ```typescript
* const modelMessages = await convertToModelMessages(uiMessages);
* const result = await generateText({ model, messages: modelMessages });
* ```
*/
export async function convertToModelMessages(
messages: Array<Omit<UIMessage, 'id'>>,
options?: {
tools?: Record<string, unknown>;
ignoreIncompleteToolCalls?: boolean;
}
): Promise<ModelMessage[]> {
const modelMessages: ModelMessage[] = [];
for (const message of messages) {
switch (message.role) {
case 'system': {
const textParts = message.parts.filter(
(part): part is TextUIPart => part.type === 'text'
);
if (textParts.length > 0) {
modelMessages.push({
role: 'system',
content: textParts.map(p => p.text).join(''),
});
}
break;
}
case 'user': {
const content: ModelMessageContent[] = [];
for (const part of message.parts) {
if (part.type === 'text') {
content.push({ type: 'text', text: part.text });
} else if (part.type === 'file') {
content.push({
type: 'file',
data: part.data || '',
mimeType: part.mediaType,
});
}
}
if (content.length > 0) {
modelMessages.push({
role: 'user',
content: content.length === 1 && content[0].type === 'text'
? content[0].text
: content,
});
}
break;
}
case 'assistant': {
const content: ModelMessageContent[] = [];
const toolCalls: ToolCall[] = [];
for (const part of message.parts) {
if (part.type === 'text') {
content.push({ type: 'text', text: part.text });
} else if (part.type === 'tool') {
// Skip incomplete tool calls if requested
if (options?.ignoreIncompleteToolCalls &&
(part.state === 'input-streaming' || part.state === 'input-available')) {
continue;
}
toolCalls.push({
id: part.toolInvocationId,
type: 'function',
function: {
name: part.toolName,
arguments: JSON.stringify(part.input || {}),
},
});
}
}
if (content.length > 0 || toolCalls.length > 0) {
modelMessages.push({
role: 'assistant',
content: content.length === 1 && content[0].type === 'text'
? content[0].text
: content.length > 0 ? content : '',
...(toolCalls.length > 0 ? { toolCalls } : {}),
});
}
// Add tool results as separate messages
for (const part of message.parts) {
if (part.type === 'tool' && part.state === 'output-available' && part.output !== undefined) {
modelMessages.push({
role: 'tool',
content: typeof part.output === 'string' ? part.output : JSON.stringify(part.output),
toolCallId: part.toolInvocationId,
});
}
}
break;
}
}
}
return modelMessages;
}
/**
* Convert model messages to UI messages.
*/
export function convertToUIMessages(
messages: ModelMessage[],
generateId: () => string = () => crypto.randomUUID()
): UIMessage[] {
const uiMessages: UIMessage[] = [];
for (const message of messages) {
const parts: UIMessagePart[] = [];
if (typeof message.content === 'string') {
parts.push({ type: 'text', text: message.content, state: 'done' });
} else if (Array.isArray(message.content)) {
for (const content of message.content) {
if (content.type === 'text') {
parts.push({ type: 'text', text: content.text, state: 'done' });
}
}
}
if (message.toolCalls) {
for (const toolCall of message.toolCalls) {
parts.push({
type: 'tool',
toolInvocationId: toolCall.id,
toolName: toolCall.function.name,
state: 'input-available',
input: JSON.parse(toolCall.function.arguments || '{}'),
});
}
}
if (message.role !== 'tool') {
uiMessages.push({
id: generateId(),
role: message.role as 'system' | 'user' | 'assistant',
parts,
});
}
}
return uiMessages;
}
// ============================================================================
// Validation
// ============================================================================
/**
* Validate UI messages array.
*/
export function validateUIMessages(messages: unknown[]): messages is UIMessage[] {
if (!Array.isArray(messages)) return false;
for (const msg of messages) {
if (!msg || typeof msg !== 'object') return false;
const m = msg as Record<string, unknown>;
if (typeof m.id !== 'string') return false;
if (!['system', 'user', 'assistant'].includes(m.role as string)) return false;
if (!Array.isArray(m.parts)) return false;
}
return true;
}
/**
* Safe validation that returns result object.
*/
export function safeValidateUIMessages(messages: unknown[]): {
success: boolean;
value?: UIMessage[];
error?: string;
} {
try {
if (validateUIMessages(messages)) {
return { success: true, value: messages };
}
return { success: false, error: 'Invalid UI messages format' };
} catch (error) {
return { success: false, error: String(error) };
}
}
// ============================================================================
// Helper Functions
// ============================================================================
/**
* Create a text UI message.
*/
export function createTextMessage(
role: 'user' | 'assistant',
text: string,
id?: string
): UIMessage {
return {
id: id || crypto.randomUUID(),
role,
parts: [{ type: 'text', text, state: 'done' }],
};
}
/**
* Create a system message.
*/
export function createSystemMessage(text: string, id?: string): UIMessage {
return {
id: id || crypto.randomUUID(),
role: 'system',
parts: [{ type: 'text', text, state: 'done' }],
};
}
/**
* Check if a message has pending tool approvals.
*/
export function hasPendingApprovals(message: UIMessage): boolean {
return message.parts.some(
part => part.type === 'tool' && part.needsApproval && part.approvalStatus === 'pending'
);
}
/**
* Get tool parts that need approval.
*/
export function getToolsNeedingApproval(message: UIMessage): ToolUIPart[] {
return message.parts.filter(
(part): part is ToolUIPart =>
part.type === 'tool' && part.needsApproval === true && part.approvalStatus === 'pending'
);
}
/**
* Create an approval response for a tool.
*/
export function createApprovalResponse(
toolInvocationId: string,
approved: boolean
): { toolInvocationId: string; approved: boolean } {
return { toolInvocationId, approved };
}
// ============================================================================
// Stream Response Helpers
// ============================================================================
export interface UIMessageStreamOptions {
/** Called when a message is created */
onMessage?: (message: UIMessage) => void;
/** Called when a part is added */
onPart?: (part: UIMessagePart) => void;
/** Called on error */
onError?: (error: Error) => void;
/** Called when stream completes */
onFinish?: (messages: UIMessage[]) => void;
}
/**
* Create a UI message stream response for Next.js route handlers.
*
* @example
* ```typescript
* // In app/api/chat/route.ts
* export async function POST(req: Request) {
* const { messages } = await req.json();
* const result = await streamText({ model, messages });
* return toUIMessageStreamResponse(result);
* }
* ```
*/
export function toUIMessageStreamResponse(
stream: AsyncIterable<any>,
options?: {
headers?: Record<string, string>;
status?: number;
}
): Response {
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
try {
for await (const chunk of stream) {
const data = JSON.stringify(chunk);
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
}
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
controller.close();
} catch (error) {
controller.error(error);
}
},
});
return new Response(readable, {
status: options?.status || 200,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
...options?.headers,
},
});
}
/**
* Pipe UI message stream to a Node.js response.
*/
export function pipeUIMessageStreamToResponse(
stream: AsyncIterable<any>,
response: { write: (chunk: string) => void; end: () => void }
): void {
(async () => {
try {
for await (const chunk of stream) {
const data = JSON.stringify(chunk);
response.write(`data: ${data}\n\n`);
}
response.write('data: [DONE]\n\n');
response.end();
} catch (error) {
response.write(`data: ${JSON.stringify({ error: String(error) })}\n\n`);
response.end();
}
})();
}
@@ -0,0 +1,272 @@
/**
* Approval command - Tool approval management for AI SDK v6 parity
*/
import {
getApprovalManager,
ApprovalManager,
createCLIApprovalPrompt
} from '../../ai/tool-approval';
import { outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
import { EXIT_CODES } from '../spec/cli-spec';
import { ERROR_CODES } from '../output/errors';
export interface ApprovalOptions {
verbose?: boolean;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
timeout?: number;
}
export async function execute(args: string[], options: ApprovalOptions): Promise<void> {
const action = args[0] || 'status';
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
try {
switch (action) {
case 'status':
await showStatus(outputFormat);
break;
case 'pending':
await showPending(outputFormat);
break;
case 'approve':
await approveRequest(args[1], outputFormat);
break;
case 'deny':
await denyRequest(args[1], outputFormat);
break;
case 'cancel':
await cancelRequest(args[1], outputFormat);
break;
case 'cancel-all':
await cancelAll(outputFormat);
break;
case 'auto-approve':
await addAutoApprove(args.slice(1), outputFormat);
break;
case 'auto-deny':
await addAutoDeny(args.slice(1), outputFormat);
break;
case 'interactive':
await startInteractive(options);
break;
case 'help':
default:
await showHelp(outputFormat);
break;
}
} catch (error) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.UNKNOWN, error instanceof Error ? error.message : String(error)));
} else {
await pretty.error(error instanceof Error ? error.message : String(error));
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
}
async function showStatus(outputFormat: string): Promise<void> {
const manager = getApprovalManager();
const pending = manager.getPendingRequests();
if (outputFormat === 'json') {
outputJson(formatSuccess({
pendingCount: pending.length,
pending: pending.map(r => ({
requestId: r.requestId,
toolName: r.toolName,
timestamp: r.timestamp,
})),
}));
} else {
await pretty.heading('Approval Status');
await pretty.plain(`Pending requests: ${pending.length}`);
if (pending.length > 0) {
await pretty.plain('\nPending:');
for (const req of pending) {
await pretty.plain(` - ${req.toolName} (${req.requestId})`);
}
}
}
}
async function showPending(outputFormat: string): Promise<void> {
const manager = getApprovalManager();
const pending = manager.getPendingRequests();
if (outputFormat === 'json') {
outputJson(formatSuccess({ pending }));
} else {
if (pending.length === 0) {
await pretty.plain('No pending approval requests');
return;
}
await pretty.heading('Pending Approval Requests');
for (const req of pending) {
await pretty.plain(`\n🔐 ${req.toolName}`);
await pretty.plain(` ID: ${req.requestId}`);
await pretty.plain(` Input: ${JSON.stringify(req.input)}`);
await pretty.plain(` Time: ${new Date(req.timestamp).toISOString()}`);
if (req.reason) {
await pretty.plain(` Reason: ${req.reason}`);
}
}
}
}
async function approveRequest(requestId: string, outputFormat: string): Promise<void> {
if (!requestId) {
throw new Error('Request ID required. Usage: praisonai approval approve <requestId>');
}
const manager = getApprovalManager();
manager.respond({
requestId,
approved: true,
timestamp: Date.now(),
});
if (outputFormat === 'json') {
outputJson(formatSuccess({ requestId, approved: true }));
} else {
await pretty.success(`Approved request: ${requestId}`);
}
}
async function denyRequest(requestId: string, outputFormat: string): Promise<void> {
if (!requestId) {
throw new Error('Request ID required. Usage: praisonai approval deny <requestId>');
}
const manager = getApprovalManager();
manager.respond({
requestId,
approved: false,
timestamp: Date.now(),
});
if (outputFormat === 'json') {
outputJson(formatSuccess({ requestId, approved: false }));
} else {
await pretty.success(`Denied request: ${requestId}`);
}
}
async function cancelRequest(requestId: string, outputFormat: string): Promise<void> {
if (!requestId) {
throw new Error('Request ID required. Usage: praisonai approval cancel <requestId>');
}
const manager = getApprovalManager();
manager.cancel(requestId);
if (outputFormat === 'json') {
outputJson(formatSuccess({ requestId, cancelled: true }));
} else {
await pretty.success(`Cancelled request: ${requestId}`);
}
}
async function cancelAll(outputFormat: string): Promise<void> {
const manager = getApprovalManager();
const pending = manager.getPendingRequests();
const count = pending.length;
manager.cancelAll();
if (outputFormat === 'json') {
outputJson(formatSuccess({ cancelled: count }));
} else {
await pretty.success(`Cancelled ${count} pending requests`);
}
}
async function addAutoApprove(args: string[], outputFormat: string): Promise<void> {
const toolName = args[0];
if (!toolName) {
throw new Error('Tool name required. Usage: praisonai approval auto-approve <toolName>');
}
const manager = getApprovalManager();
manager.addAutoApprove(toolName);
if (outputFormat === 'json') {
outputJson(formatSuccess({ toolName, autoApprove: true }));
} else {
await pretty.success(`Added auto-approve for: ${toolName}`);
}
}
async function addAutoDeny(args: string[], outputFormat: string): Promise<void> {
const toolName = args[0];
if (!toolName) {
throw new Error('Tool name required. Usage: praisonai approval auto-deny <toolName>');
}
const manager = getApprovalManager();
manager.addAutoDeny(toolName);
if (outputFormat === 'json') {
outputJson(formatSuccess({ toolName, autoDeny: true }));
} else {
await pretty.success(`Added auto-deny for: ${toolName}`);
}
}
async function startInteractive(options: ApprovalOptions): Promise<void> {
await pretty.heading('Interactive Approval Mode');
await pretty.plain('Waiting for approval requests... (Ctrl+C to exit)\n');
const manager = getApprovalManager();
const handler = createCLIApprovalPrompt();
manager.onApprovalRequest(handler);
// Keep process alive
await new Promise(() => {});
}
async function showHelp(outputFormat: string): Promise<void> {
const help = {
command: 'approval',
description: 'Manage tool approval requests (AI SDK v6 parity)',
subcommands: [
{ name: 'status', description: 'Show approval status' },
{ name: 'pending', description: 'List pending approval requests' },
{ name: 'approve <id>', description: 'Approve a pending request' },
{ name: 'deny <id>', description: 'Deny a pending request' },
{ name: 'cancel <id>', description: 'Cancel a pending request' },
{ name: 'cancel-all', description: 'Cancel all pending requests' },
{ name: 'auto-approve <tool>', description: 'Auto-approve a tool' },
{ name: 'auto-deny <tool>', description: 'Auto-deny a tool' },
{ name: 'interactive', description: 'Start interactive approval mode' },
],
examples: [
'praisonai approval status',
'praisonai approval pending',
'praisonai approval approve abc123',
'praisonai approval auto-approve readFile',
'praisonai approval interactive',
],
};
if (outputFormat === 'json') {
outputJson(formatSuccess(help));
} else {
await pretty.heading('Approval Command');
await pretty.plain('Manage tool approval requests for safe AI agent execution.\n');
await pretty.plain('Subcommands:');
for (const cmd of help.subcommands) {
await pretty.plain(` ${cmd.name.padEnd(20)} ${cmd.description}`);
}
await pretty.plain('\nExamples:');
for (const ex of help.examples) {
await pretty.plain(` ${ex}`);
}
}
}
@@ -1,16 +1,29 @@
/**
* Observability command - Monitoring and tracing
*
* Supports 14+ observability integrations:
* - list: List all available observability tools
* - doctor: Check environment variables for selected tool
* - test: Run a test trace to verify tool works
* - info: Show observability feature information
*/
import { outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
import { EXIT_CODES } from '../spec/cli-spec';
import { ERROR_CODES } from '../output/errors';
import {
OBSERVABILITY_TOOLS,
listObservabilityTools,
hasObservabilityToolEnvVar,
type ObservabilityToolName
} from '../../observability/types';
export interface ObservabilityOptions {
verbose?: boolean;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
tool?: string;
}
export async function execute(args: string[], options: ObservabilityOptions): Promise<void> {
@@ -19,8 +32,15 @@ export async function execute(args: string[], options: ObservabilityOptions): Pr
try {
switch (action) {
case 'list':
case 'providers':
await listProviders(outputFormat);
await listProvidersCommand(options, outputFormat);
break;
case 'doctor':
await doctorCommand(args[1] || options.tool, options, outputFormat);
break;
case 'test':
await testCommand(args[1] || options.tool, options, outputFormat);
break;
case 'info':
await showInfo(outputFormat);
@@ -76,20 +96,211 @@ async function showInfo(outputFormat: string): Promise<void> {
}
}
async function listProviders(outputFormat: string): Promise<void> {
const providers = [
{ name: 'console', description: 'Console logging (default)', available: true },
{ name: 'memory', description: 'In-memory storage', available: true },
{ name: 'langfuse', description: 'Langfuse observability platform', available: true }
];
/**
* List all observability tools
*/
async function listProvidersCommand(options: ObservabilityOptions, outputFormat: string): Promise<void> {
const tools = listObservabilityTools();
const providers = tools.map(tool => ({
name: tool.name,
description: tool.description,
package: tool.package,
envKey: tool.envKey,
hasEnvKey: hasObservabilityToolEnvVar(tool.name),
features: tool.features
}));
const ready = providers.filter(p => p.hasEnvKey);
const builtIn = providers.filter(p => ['console', 'memory', 'noop'].includes(p.name));
const external = providers.filter(p => !['console', 'memory', 'noop'].includes(p.name));
if (outputFormat === 'json') {
outputJson(formatSuccess({ providers }));
outputJson(formatSuccess({
providers,
total: providers.length,
ready: ready.length,
builtin: builtIn.length,
external: external.length
}));
} else {
await pretty.heading('Observability Providers');
for (const p of providers) {
const status = p.available ? '✓' : '✗';
await pretty.plain(` ${status} ${p.name.padEnd(15)} ${p.description}`);
await pretty.heading('Observability Tools');
await pretty.plain(`\n Total: ${providers.length} tools (${ready.length} ready)`);
await pretty.plain('\n Built-in (no setup required):');
for (const p of builtIn) {
await pretty.plain(` ✅ ${p.name.padEnd(12)} ${p.description}`);
}
await pretty.plain('\n External Integrations:');
for (const p of external) {
const status = p.hasEnvKey ? '✅' : '⚠️';
const keyInfo = p.envKey ? ` (${p.envKey})` : '';
await pretty.plain(` ${status} ${p.name.padEnd(12)} ${p.description}${options.verbose ? keyInfo : ''}`);
}
await pretty.newline();
await pretty.info('Run "observability doctor <tool>" for setup instructions');
}
}
/**
* Doctor command - Check environment for a tool
*/
async function doctorCommand(toolName: string | undefined, options: ObservabilityOptions, outputFormat: string): Promise<void> {
if (!toolName) {
// Check all tools
const tools = listObservabilityTools();
const results = tools.map(tool => ({
name: tool.name,
envKey: tool.envKey,
hasKey: hasObservabilityToolEnvVar(tool.name),
package: tool.package
}));
const ready = results.filter(r => r.hasKey);
if (outputFormat === 'json') {
outputJson(formatSuccess({
total: results.length,
ready: ready.length,
tools: results
}));
} else {
await pretty.heading('Observability Environment Check');
await pretty.plain(`\n Total Tools: ${results.length}`);
await pretty.plain(` Ready: ${ready.length} ✅`);
if (ready.length > 0) {
await pretty.plain('\n Ready Tools:');
for (const t of ready) {
await pretty.plain(` ✅ ${t.name}`);
}
}
await pretty.newline();
await pretty.info('Run "observability doctor <tool>" for specific tool setup');
}
return;
}
// Check specific tool
const tool = OBSERVABILITY_TOOLS[toolName.toLowerCase() as ObservabilityToolName];
if (!tool) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.INVALID_ARGS, `Unknown tool: ${toolName}`));
} else {
await pretty.error(`Unknown tool: ${toolName}`);
await pretty.info(`Available: ${Object.keys(OBSERVABILITY_TOOLS).join(', ')}`);
}
return;
}
const hasKey = hasObservabilityToolEnvVar(tool.name);
const keyValue = tool.envKey ? process.env[tool.envKey] : null;
const maskedKey = keyValue ? `${keyValue.slice(0, 4)}...${keyValue.slice(-4)}` : 'not set';
if (outputFormat === 'json') {
outputJson(formatSuccess({
tool: tool.name,
env_key: tool.envKey,
has_key: hasKey,
key_preview: hasKey ? maskedKey : null,
package: tool.package,
description: tool.description,
features: tool.features,
status: hasKey || !tool.envKey ? 'ready' : 'missing_key'
}));
} else {
await pretty.heading(`Observability Doctor: ${tool.name}`);
await pretty.plain(` Description: ${tool.description}`);
if (tool.package) {
await pretty.plain(` Package: ${tool.package}`);
}
if (tool.envKey) {
await pretty.plain(` Environment Variable: ${tool.envKey}`);
await pretty.plain(` Status: ${hasKey ? '✅ Ready' : '❌ Missing API Key'}`);
if (hasKey) {
await pretty.dim(` Key Preview: ${maskedKey}`);
} else {
await pretty.newline();
await pretty.info(`Set the API key with:`);
await pretty.dim(` export ${tool.envKey}=your-api-key`);
}
} else {
await pretty.plain(` Status: ✅ Ready (no API key required)`);
}
await pretty.newline();
await pretty.plain(' Features:');
const f = tool.features;
await pretty.plain(` Traces: ${f.traces ? '✅' : '❌'} Spans: ${f.spans ? '✅' : '❌'} Events: ${f.events ? '✅' : '❌'}`);
await pretty.plain(` Errors: ${f.errors ? '✅' : '❌'} Metrics: ${f.metrics ? '✅' : '❌'} Export: ${f.export ? '✅' : '❌'}`);
}
}
/**
* Test command - Run a test trace
*/
async function testCommand(toolName: string | undefined, options: ObservabilityOptions, outputFormat: string): Promise<void> {
const tool = toolName?.toLowerCase() || 'memory';
if (!OBSERVABILITY_TOOLS[tool as ObservabilityToolName]) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.INVALID_ARGS, `Unknown tool: ${toolName}`));
} else {
await pretty.error(`Unknown tool: ${toolName}`);
}
return;
}
if (outputFormat !== 'json') {
await pretty.info(`Testing ${tool} observability...`);
}
const startTime = Date.now();
try {
const { createObservabilityAdapter } = await import('../../observability/adapters');
const adapter = await createObservabilityAdapter(tool as ObservabilityToolName);
// Run a test trace
const trace = adapter.startTrace('test-trace', { test: true });
const span = adapter.startSpan(trace.traceId, 'test-span', 'custom');
adapter.addEvent(span.spanId, 'test-event', { message: 'hello' });
adapter.endSpan(span.spanId, 'completed');
adapter.endTrace(trace.traceId, 'completed');
await adapter.flush();
const latency = Date.now() - startTime;
if (outputFormat === 'json') {
outputJson(formatSuccess({
tool,
status: 'success',
latency_ms: latency,
trace_id: trace.traceId
}));
} else {
await pretty.plain(`\n ✅ Test Passed`);
await pretty.plain(` Tool: ${tool}`);
await pretty.plain(` Latency: ${latency}ms`);
await pretty.dim(` Trace ID: ${trace.traceId}`);
}
} catch (error: any) {
const latency = Date.now() - startTime;
const errorMessage = error.message || String(error);
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.UNKNOWN, errorMessage, {
tool,
latency_ms: latency
}));
} else {
await pretty.error(`Test Failed: ${errorMessage}`);
await pretty.plain(` Tool: ${tool}`);
await pretty.plain(` Latency: ${latency}ms`);
}
}
}
+233 -5
View File
@@ -2,8 +2,9 @@
* Providers command - List and manage LLM providers
*
* Supports:
* - list: List all registered providers
* - register: Register a custom provider (programmatic only)
* - list: List all registered providers (60+ AI SDK v6 providers)
* - doctor: Check environment variables for selected provider
* - test: Run a small test request to verify provider works
* - info: Show provider details
*/
@@ -14,6 +15,12 @@ import {
getDefaultRegistry,
isProviderAvailable
} from '../../llm/providers';
import {
AISDK_PROVIDERS,
PROVIDER_ALIASES,
COMMUNITY_PROVIDERS,
type ProviderInfo as AISDKProviderInfo
} from '../../llm/providers/ai-sdk/types';
import { hasApiKey } from '../runtime/env';
import { outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
@@ -23,6 +30,8 @@ export interface ProvidersOptions {
verbose?: boolean;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
provider?: string;
model?: string;
}
interface ProviderInfo {
@@ -32,14 +41,31 @@ interface ProviderInfo {
hasApiKey: boolean;
isBuiltin: boolean;
models?: string[];
modalities?: {
text: boolean;
chat: boolean;
embeddings: boolean;
image: boolean;
audio: boolean;
speech: boolean;
tools: boolean;
};
description?: string;
envKey?: string;
package?: string;
}
const BUILTIN_PROVIDERS = ['openai', 'anthropic', 'google', 'gemini', 'oai', 'claude'];
const PROVIDER_MODELS: Record<string, string[]> = {
openai: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'],
anthropic: ['claude-sonnet-4-20250514', 'claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-haiku-20240307'],
google: ['gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-1.5-flash']
openai: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo', 'gpt-5', 'gpt-5-mini'],
anthropic: ['claude-sonnet-4-20250514', 'claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-haiku-20240307', 'claude-opus-4-5'],
google: ['gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-1.5-flash'],
xai: ['grok-4', 'grok-3', 'grok-3-fast', 'grok-3-mini'],
groq: ['llama-3.3-70b-versatile', 'mixtral-8x7b-32768', 'gemma2-9b-it'],
mistral: ['mistral-large-latest', 'mistral-medium-latest', 'mistral-small-latest'],
deepseek: ['deepseek-chat', 'deepseek-reasoner'],
cerebras: ['llama3.1-8b', 'llama3.3-70b'],
};
export async function execute(args: string[], options: ProvidersOptions): Promise<void> {
@@ -50,6 +76,12 @@ export async function execute(args: string[], options: ProvidersOptions): Promis
case 'list':
await listProvidersCommand(options, outputFormat);
break;
case 'doctor':
await doctorCommand(args[1] || options.provider, options, outputFormat);
break;
case 'test':
await testCommand(args[1] || options.provider, args[2] || options.model, options, outputFormat);
break;
case 'info':
await providerInfoCommand(args[1], options, outputFormat);
break;
@@ -128,6 +160,202 @@ async function listProvidersCommand(options: ProvidersOptions, outputFormat: str
}
}
/**
* Doctor command - Check environment variables for a provider
*/
async function doctorCommand(providerName: string | undefined, options: ProvidersOptions, outputFormat: string): Promise<void> {
// Get all AI SDK providers
const allProviders = Object.entries(AISDK_PROVIDERS);
if (providerName) {
// Check specific provider
const resolved = PROVIDER_ALIASES[providerName.toLowerCase()] || providerName.toLowerCase();
const providerInfo = AISDK_PROVIDERS[resolved];
if (!providerInfo) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.INVALID_ARGS, `Unknown provider: ${providerName}`));
} else {
await pretty.error(`Unknown provider: ${providerName}`);
await pretty.info(`Available providers: ${Object.keys(AISDK_PROVIDERS).slice(0, 10).join(', ')}...`);
}
return;
}
const hasKey = !!process.env[providerInfo.envKey];
const keyValue = process.env[providerInfo.envKey];
const maskedKey = keyValue ? `${keyValue.slice(0, 4)}...${keyValue.slice(-4)}` : 'not set';
if (outputFormat === 'json') {
outputJson(formatSuccess({
provider: resolved,
env_key: providerInfo.envKey,
has_key: hasKey,
key_preview: hasKey ? maskedKey : null,
package: providerInfo.package,
description: providerInfo.description,
modalities: providerInfo.modalities,
status: hasKey ? 'ready' : 'missing_key'
}));
} else {
await pretty.heading(`Provider Doctor: ${resolved}`);
await pretty.plain(` Package: ${providerInfo.package}`);
await pretty.plain(` Description: ${providerInfo.description || 'N/A'}`);
await pretty.plain(` Environment Variable: ${providerInfo.envKey}`);
await pretty.plain(` Status: ${hasKey ? '✅ Ready' : '❌ Missing API Key'}`);
if (hasKey) {
await pretty.dim(` Key Preview: ${maskedKey}`);
} else {
await pretty.newline();
await pretty.info(`Set the API key with:`);
await pretty.dim(` export ${providerInfo.envKey}=your-api-key`);
}
await pretty.newline();
await pretty.plain(' Modalities:');
const m = providerInfo.modalities;
await pretty.plain(` Text/Chat: ${m.chat ? '✅' : '❌'} Embeddings: ${m.embeddings ? '✅' : '❌'} Image: ${m.image ? '✅' : '❌'}`);
await pretty.plain(` Audio: ${m.audio ? '✅' : '❌'} Speech: ${m.speech ? '✅' : '❌'} Tools: ${m.tools ? '✅' : '❌'}`);
}
} else {
// Check all providers
const results = allProviders.map(([name, info]) => ({
name,
envKey: info.envKey,
hasKey: !!process.env[info.envKey],
description: info.description
}));
const ready = results.filter(r => r.hasKey);
const missing = results.filter(r => !r.hasKey);
if (outputFormat === 'json') {
outputJson(formatSuccess({
total: results.length,
ready: ready.length,
missing: missing.length,
providers: results
}));
} else {
await pretty.heading('Provider Environment Check');
await pretty.plain(`\n Total Providers: ${results.length}`);
await pretty.plain(` Ready: ${ready.length} ✅`);
await pretty.plain(` Missing Keys: ${missing.length} ⚠️`);
if (ready.length > 0) {
await pretty.plain('\n Ready Providers:');
for (const p of ready.slice(0, 10)) {
await pretty.plain(` ✅ ${p.name}`);
}
if (ready.length > 10) {
await pretty.dim(` ... and ${ready.length - 10} more`);
}
}
await pretty.newline();
await pretty.info('Run "providers doctor <name>" for detailed info on a specific provider');
}
}
}
/**
* Test command - Run a small test request to verify provider works
*/
async function testCommand(providerName: string | undefined, modelName: string | undefined, options: ProvidersOptions, outputFormat: string): Promise<void> {
if (!providerName) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Provider name required. Usage: providers test <provider> [model]'));
} else {
await pretty.error('Provider name required. Usage: providers test <provider> [model]');
}
return;
}
const resolved = PROVIDER_ALIASES[providerName.toLowerCase()] || providerName.toLowerCase();
const providerInfo = AISDK_PROVIDERS[resolved];
if (!providerInfo) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.INVALID_ARGS, `Unknown provider: ${providerName}`));
} else {
await pretty.error(`Unknown provider: ${providerName}`);
}
return;
}
// Check API key
if (!process.env[providerInfo.envKey]) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, `Missing API key: ${providerInfo.envKey}`));
} else {
await pretty.error(`Missing API key: ${providerInfo.envKey}`);
await pretty.info(`Set it with: export ${providerInfo.envKey}=your-api-key`);
}
return;
}
// Get default model
const model = modelName || PROVIDER_MODELS[resolved]?.[0] || 'default';
const modelString = `${resolved}/${model}`;
if (outputFormat !== 'json') {
await pretty.info(`Testing ${modelString}...`);
}
const startTime = Date.now();
try {
// Try to use the backend resolver
const { resolveBackend } = await import('../../llm/backend-resolver');
const { provider } = await resolveBackend(modelString);
// Make a minimal test request
const result = await provider.generateText({
messages: [{ role: 'user', content: 'Say "test ok" and nothing else.' }],
temperature: 0,
maxTokens: 10
});
const latency = Date.now() - startTime;
const responseText = result.text?.slice(0, 50) || '[no response]';
if (outputFormat === 'json') {
outputJson(formatSuccess({
provider: resolved,
model,
status: 'success',
latency_ms: latency,
response_preview: responseText,
usage: result.usage
}));
} else {
await pretty.plain(`\n ✅ Test Passed`);
await pretty.plain(` Provider: ${resolved}`);
await pretty.plain(` Model: ${model}`);
await pretty.plain(` Latency: ${latency}ms`);
await pretty.dim(` Response: "${responseText}"`);
if (result.usage) {
await pretty.dim(` Tokens: ${result.usage.totalTokens || 'N/A'}`);
}
}
} catch (error: any) {
const latency = Date.now() - startTime;
const errorMessage = error.message || String(error);
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.UNKNOWN, errorMessage, {
provider: resolved,
model,
latency_ms: latency
}));
} else {
await pretty.error(`Test Failed: ${errorMessage}`);
await pretty.plain(` Provider: ${resolved}`);
await pretty.plain(` Model: ${model}`);
await pretty.plain(` Latency: ${latency}ms`);
}
}
}
async function providerInfoCommand(name: string | undefined, options: ProvidersOptions, outputFormat: string): Promise<void> {
if (!name) {
if (outputFormat === 'json') {
+474 -70
View File
@@ -1,8 +1,20 @@
/**
* Tools command - List or manage tools
* Tools command - List, manage, and test AI SDK tools
*
* Subcommands:
* list - List all available tools
* info - Get detailed info about a tool
* doctor - Check tool dependencies and env vars
* example - Show usage example for a tool
* add - Register a custom tool from npm/local
* test - Test a tool (dry-run or live)
*/
import { getRegistry, FunctionTool } from '../../tools/decorator';
import { getToolsRegistry } from '../../tools/registry/registry';
import { registerBuiltinTools } from '../../tools/tools';
import { getAllBuiltinMetadata } from '../../tools/builtins';
import type { ToolMetadata } from '../../tools/registry/types';
import { outputJson, formatSuccess, formatError } from '../output/json';
import * as pretty from '../output/pretty';
import { EXIT_CODES } from '../spec/cli-spec';
@@ -12,24 +24,19 @@ export interface ToolsOptions {
verbose?: boolean;
output?: 'json' | 'text' | 'pretty';
json?: boolean;
live?: boolean;
tag?: string;
}
interface ToolInfo {
name: string;
description?: string;
parameters?: unknown;
// Ensure built-in tools are registered
let builtinsRegistered = false;
function ensureBuiltinsRegistered() {
if (!builtinsRegistered) {
registerBuiltinTools();
builtinsRegistered = true;
}
}
// Built-in tools that are always available
const BUILTIN_TOOLS: ToolInfo[] = [
{ name: 'arxiv_search', description: 'Search arXiv for academic papers' },
{ name: 'web_search', description: 'Search the web for information' },
{ name: 'calculator', description: 'Perform mathematical calculations' },
{ name: 'code_executor', description: 'Execute code snippets' },
{ name: 'file_reader', description: 'Read file contents' },
{ name: 'file_writer', description: 'Write content to files' }
];
export async function execute(args: string[], options: ToolsOptions): Promise<void> {
const subcommand = args[0] || 'list';
const outputFormat = options.json ? 'json' : (options.output || 'pretty');
@@ -41,54 +48,76 @@ export async function execute(args: string[], options: ToolsOptions): Promise<vo
case 'info':
await toolInfo(args.slice(1), options, outputFormat);
break;
case 'doctor':
await toolDoctor(options, outputFormat);
break;
case 'example':
await toolExample(args.slice(1), options, outputFormat);
break;
case 'add':
await toolAdd(args.slice(1), options, outputFormat);
break;
case 'test':
await toolTest(args.slice(1), options, outputFormat);
break;
default:
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.INVALID_ARGS, `Unknown subcommand: ${subcommand}`));
} else {
await pretty.error(`Unknown subcommand: ${subcommand}`);
await pretty.plain('Available subcommands: list, info, doctor, example, add, test');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
}
async function listTools(options: ToolsOptions, outputFormat: string): Promise<void> {
// Get registered tools from registry
const registry = getRegistry();
const registeredTools = registry.list();
ensureBuiltinsRegistered();
// Combine built-in and registered tools
const allTools: ToolInfo[] = [
...BUILTIN_TOOLS,
...registeredTools.map((t: FunctionTool) => ({
name: t.name,
description: t.description,
parameters: t.parameters
}))
];
// Get all built-in tool metadata
const builtinMetadata = await getAllBuiltinMetadata();
// Get registered tools from decorator registry (legacy)
const decoratorRegistry = getRegistry();
const registeredTools = decoratorRegistry.list();
// Filter by tag if specified
let filteredMetadata = builtinMetadata;
if (options.tag) {
filteredMetadata = builtinMetadata.filter(m => m.tags.includes(options.tag!));
}
if (outputFormat === 'json') {
outputJson(formatSuccess({
tools: allTools.map(t => ({
builtinTools: filteredMetadata.map(m => ({
id: m.id,
name: m.displayName,
description: m.description,
tags: m.tags,
packageName: m.packageName,
requiredEnv: m.requiredEnv,
capabilities: m.capabilities,
})),
registeredTools: registeredTools.map((t: FunctionTool) => ({
name: t.name,
description: t.description,
parameters: options.verbose ? t.parameters : undefined
})),
count: allTools.length
count: filteredMetadata.length + registeredTools.length
}));
} else {
await pretty.heading('Available Tools');
await pretty.heading('AI SDK Tools Registry');
await pretty.plain('Built-in Tools:');
for (const tool of BUILTIN_TOOLS) {
await pretty.plain(` • ${tool.name}`);
if (tool.description) {
await pretty.dim(` ${tool.description}`);
}
await pretty.plain('\nBuilt-in Tools:');
for (const meta of filteredMetadata) {
const tags = meta.tags.slice(0, 3).join(', ');
await pretty.plain(` • ${meta.id} (${meta.displayName})`);
await pretty.dim(` ${meta.description}`);
await pretty.dim(` Tags: ${tags} | Package: ${meta.packageName}`);
}
if (registeredTools.length > 0) {
await pretty.newline();
await pretty.plain('Registered Tools:');
await pretty.plain('Custom Registered Tools:');
for (const tool of registeredTools) {
await pretty.plain(` • ${tool.name}`);
if (tool.description) {
@@ -98,63 +127,438 @@ async function listTools(options: ToolsOptions, outputFormat: string): Promise<v
}
await pretty.newline();
await pretty.info(`Total: ${allTools.length} tools`);
await pretty.info(`Total: ${filteredMetadata.length + registeredTools.length} tools`);
await pretty.dim('\nUse "praisonai-ts tools info <id>" for details');
await pretty.dim('Use "praisonai-ts tools doctor" to check dependencies');
}
}
async function toolInfo(args: string[], options: ToolsOptions, outputFormat: string): Promise<void> {
const toolName = args[0];
ensureBuiltinsRegistered();
const toolId = args[0];
if (!toolName) {
if (!toolId) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide a tool name'));
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide a tool ID'));
} else {
await pretty.error('Please provide a tool name');
await pretty.error('Please provide a tool ID');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
// Look for tool in built-in and registered tools
const builtinTool = BUILTIN_TOOLS.find(t => t.name === toolName);
const registry = getRegistry();
const registeredTool = registry.get(toolName);
// Look for tool in built-in metadata
const builtinMetadata = await getAllBuiltinMetadata();
const metadata = builtinMetadata.find(m => m.id === toolId);
const tool = builtinTool || (registeredTool ? {
name: registeredTool.name,
description: registeredTool.description,
parameters: registeredTool.parameters
} : null);
// Also check decorator registry
const decoratorRegistry = getRegistry();
const registeredTool = decoratorRegistry.get(toolId);
if (!tool) {
if (!metadata && !registeredTool) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.TOOL_NOT_FOUND, `Tool not found: ${toolName}`));
outputJson(formatError(ERROR_CODES.TOOL_NOT_FOUND, `Tool not found: ${toolId}`));
} else {
await pretty.error(`Tool not found: ${toolName}`);
await pretty.error(`Tool not found: ${toolId}`);
await pretty.dim('Use "praisonai-ts tools list" to see available tools');
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
if (outputFormat === 'json') {
outputJson(formatSuccess({
tool: {
name: tool.name,
description: tool.description,
parameters: tool.parameters,
type: builtinTool ? 'builtin' : 'registered'
tool: metadata ? {
id: metadata.id,
name: metadata.displayName,
description: metadata.description,
tags: metadata.tags,
packageName: metadata.packageName,
requiredEnv: metadata.requiredEnv,
optionalEnv: metadata.optionalEnv,
install: metadata.install,
capabilities: metadata.capabilities,
docsSlug: metadata.docsSlug,
} : {
name: registeredTool!.name,
description: registeredTool!.description,
type: 'registered'
}
}));
} else {
await pretty.heading(`Tool: ${tool.name}`);
await pretty.keyValue({
'Name': tool.name,
'Type': builtinTool ? 'Built-in' : 'Registered',
'Description': tool.description || 'No description'
});
if (tool.parameters && options.verbose) {
if (metadata) {
await pretty.heading(`Tool: ${metadata.displayName}`);
await pretty.keyValue({
'ID': metadata.id,
'Description': metadata.description,
'Package': metadata.packageName,
'Tags': metadata.tags.join(', '),
'Docs': `https://docs.praison.ai/js/${metadata.docsSlug}`,
});
await pretty.newline();
await pretty.plain('Parameters:');
await pretty.plain(JSON.stringify(tool.parameters, null, 2));
await pretty.plain('Required Environment Variables:');
if (metadata.requiredEnv.length > 0) {
for (const env of metadata.requiredEnv) {
const isSet = !!process.env[env];
await pretty.plain(` ${isSet ? '✓' : '✗'} ${env}`);
}
} else {
await pretty.dim(' None');
}
await pretty.newline();
await pretty.plain('Installation:');
await pretty.plain(` ${metadata.install.npm}`);
await pretty.newline();
await pretty.plain('Capabilities:');
const caps = Object.entries(metadata.capabilities)
.filter(([_, v]) => v)
.map(([k]) => k);
await pretty.plain(` ${caps.join(', ') || 'None specified'}`);
} else {
await pretty.heading(`Tool: ${registeredTool!.name}`);
await pretty.keyValue({
'Name': registeredTool!.name,
'Type': 'Custom Registered',
'Description': registeredTool!.description || 'No description'
});
}
}
}
async function toolDoctor(options: ToolsOptions, outputFormat: string): Promise<void> {
ensureBuiltinsRegistered();
const builtinMetadata = await getAllBuiltinMetadata();
const results: Array<{
id: string;
name: string;
installed: boolean;
missingEnv: string[];
installCmd?: string;
}> = [];
for (const meta of builtinMetadata) {
// Check env vars
const missingEnv = meta.requiredEnv.filter(env => !process.env[env]);
// Check if package is installed (check node_modules)
let installed = false;
try {
// First try require.resolve for CJS packages
require.resolve(meta.packageName);
installed = true;
} catch {
// For ESM packages, check if the package directory exists
try {
const path = await import('path');
const fs = await import('fs');
const packagePath = path.join(process.cwd(), 'node_modules', ...meta.packageName.split('/'));
installed = fs.existsSync(packagePath);
} catch {
installed = false;
}
}
results.push({
id: meta.id,
name: meta.displayName,
installed,
missingEnv,
installCmd: installed ? undefined : meta.install.npm,
});
}
const allGood = results.every(r => r.installed && r.missingEnv.length === 0);
const installedCount = results.filter(r => r.installed).length;
const envIssues = results.filter(r => r.missingEnv.length > 0).length;
if (outputFormat === 'json') {
outputJson(formatSuccess({
status: allGood ? 'healthy' : 'issues_found',
summary: {
total: results.length,
installed: installedCount,
notInstalled: results.length - installedCount,
missingEnvVars: envIssues,
},
tools: results,
}));
} else {
await pretty.heading('Tools Health Check');
await pretty.newline();
await pretty.plain(`Summary: ${installedCount}/${results.length} packages installed, ${envIssues} with missing env vars`);
await pretty.newline();
for (const result of results) {
const status = result.installed && result.missingEnv.length === 0 ? '✓' : '✗';
await pretty.plain(`${status} ${result.id} (${result.name})`);
if (!result.installed) {
await pretty.dim(` Package not installed: ${result.installCmd}`);
}
if (result.missingEnv.length > 0) {
await pretty.dim(` Missing env vars: ${result.missingEnv.join(', ')}`);
}
}
await pretty.newline();
if (allGood) {
await pretty.info('All tools are ready to use!');
} else {
await pretty.warn('Some tools need attention. Install packages and set env vars as needed.');
}
}
}
async function toolExample(args: string[], options: ToolsOptions, outputFormat: string): Promise<void> {
const toolId = args[0];
if (!toolId) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide a tool ID'));
} else {
await pretty.error('Please provide a tool ID');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
// Tool examples
const examples: Record<string, string> = {
'code-execution': `
import { Agent, tools } from 'praisonai';
const agent = new Agent({
name: 'CodeRunner',
instructions: 'You can execute Python code to solve problems.',
tools: [tools.codeExecution()],
});
const result = await agent.run('Calculate the factorial of 10');
console.log(result.text);
`,
'tavily': `
import { Agent, tools } from 'praisonai';
const agent = new Agent({
name: 'Researcher',
instructions: 'Search the web for information.',
tools: [tools.tavily()],
});
const result = await agent.run('What are the latest AI developments?');
console.log(result.text);
`,
'exa': `
import { Agent, tools } from 'praisonai';
const agent = new Agent({
name: 'WebSearcher',
instructions: 'Search the web using semantic search.',
tools: [tools.exa()],
});
const result = await agent.run('Find AI companies in Europe');
console.log(result.text);
`,
'superagent': `
import { Agent, tools } from 'praisonai';
const agent = new Agent({
name: 'SecureAgent',
instructions: 'Process text securely.',
tools: [
tools.guard(), // Check for prompt injection
tools.redact(), // Remove PII
tools.verify(), // Verify claims
],
});
const result = await agent.run('Check this text for security issues');
console.log(result.text);
`,
'valyu': `
import { Agent, tools } from 'praisonai';
const agent = new Agent({
name: 'FinanceResearcher',
instructions: 'Research financial data.',
tools: [
tools.valyuFinanceSearch(),
tools.valyuSecSearch(),
],
});
const result = await agent.run('What is Apple stock price trend?');
console.log(result.text);
`,
};
const example = examples[toolId];
if (!example) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.TOOL_NOT_FOUND, `No example for tool: ${toolId}`));
} else {
await pretty.error(`No example available for: ${toolId}`);
await pretty.dim(`Available examples: ${Object.keys(examples).join(', ')}`);
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
if (outputFormat === 'json') {
outputJson(formatSuccess({ toolId, example: example.trim() }));
} else {
await pretty.heading(`Example: ${toolId}`);
await pretty.plain(example);
}
}
async function toolAdd(args: string[], options: ToolsOptions, outputFormat: string): Promise<void> {
const packageOrPath = args[0];
if (!packageOrPath) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide a package name or path'));
} else {
await pretty.error('Please provide a package name or path');
await pretty.dim('Usage: praisonai-ts tools add <package-name>');
await pretty.dim(' praisonai-ts tools add ./path/to/tool.js');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
try {
const { registerNpmTool, registerLocalTool } = await import('../../tools/builtins/custom');
const isLocalPath = packageOrPath.startsWith('./') || packageOrPath.startsWith('/');
const tool = isLocalPath
? await registerLocalTool(packageOrPath)
: await registerNpmTool(packageOrPath);
if (outputFormat === 'json') {
outputJson(formatSuccess({
registered: true,
tool: {
name: tool.name,
description: tool.description,
source: isLocalPath ? 'local' : 'npm',
}
}));
} else {
await pretty.info(`Successfully registered tool: ${tool.name}`);
await pretty.dim(`Description: ${tool.description}`);
}
} catch (error) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.RUNTIME_ERROR, `Failed to register tool: ${error}`));
} else {
await pretty.error(`Failed to register tool: ${error}`);
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
}
async function toolTest(args: string[], options: ToolsOptions, outputFormat: string): Promise<void> {
const toolId = args[0];
if (!toolId) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, 'Please provide a tool ID'));
} else {
await pretty.error('Please provide a tool ID');
}
process.exit(EXIT_CODES.INVALID_ARGUMENTS);
}
ensureBuiltinsRegistered();
const registry = getToolsRegistry();
const metadata = registry.getMetadata(toolId);
if (!metadata) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.TOOL_NOT_FOUND, `Tool not found: ${toolId}`));
} else {
await pretty.error(`Tool not found: ${toolId}`);
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
// Check dependencies
const missingEnv = metadata.requiredEnv.filter(env => !process.env[env]);
if (missingEnv.length > 0 && !options.live) {
if (outputFormat === 'json') {
outputJson(formatSuccess({
toolId,
status: 'dry_run',
message: 'Dry run completed. Missing env vars for live test.',
missingEnv,
}));
} else {
await pretty.info(`Dry run for ${toolId}:`);
await pretty.plain(` Tool ID: ${metadata.id}`);
await pretty.plain(` Package: ${metadata.packageName}`);
await pretty.warn(` Missing env vars: ${missingEnv.join(', ')}`);
await pretty.dim('\nUse --live flag to run actual test (requires env vars)');
}
return;
}
if (options.live) {
if (missingEnv.length > 0) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.MISSING_ARG, `Missing required env vars: ${missingEnv.join(', ')}`));
} else {
await pretty.error(`Cannot run live test. Missing env vars: ${missingEnv.join(', ')}`);
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
try {
const tool = registry.create(toolId);
await pretty.info(`Live test for ${toolId}...`);
// Simple test execution
const testInput = { query: 'test' };
const result = await tool.execute(testInput);
if (outputFormat === 'json') {
outputJson(formatSuccess({
toolId,
status: 'success',
result,
}));
} else {
await pretty.info('Test passed!');
await pretty.plain(`Result: ${JSON.stringify(result, null, 2)}`);
}
} catch (error) {
if (outputFormat === 'json') {
outputJson(formatError(ERROR_CODES.RUNTIME_ERROR, `Test failed: ${error}`));
} else {
await pretty.error(`Test failed: ${error}`);
}
process.exit(EXIT_CODES.RUNTIME_ERROR);
}
} else {
if (outputFormat === 'json') {
outputJson(formatSuccess({
toolId,
status: 'dry_run',
message: 'Dry run completed successfully.',
metadata: {
id: metadata.id,
package: metadata.packageName,
envVars: metadata.requiredEnv,
}
}));
} else {
await pretty.info(`Dry run for ${toolId}: OK`);
await pretty.plain(` Tool is registered and ready`);
await pretty.dim('\nUse --live flag to run actual API test');
}
}
}
+17
View File
@@ -228,6 +228,23 @@ export const COMMANDS: Record<string, Command> = {
export: { description: 'Export telemetry data' }
}
},
approval: {
description: 'Tool approval management (AI SDK v6 parity)',
subcommands: {
status: { description: 'Show approval status' },
pending: { description: 'List pending approval requests' },
approve: { description: 'Approve a pending request', args: [{ name: 'id', type: 'string', required: true, position: 0 }] },
deny: { description: 'Deny a pending request', args: [{ name: 'id', type: 'string', required: true, position: 0 }] },
cancel: { description: 'Cancel a pending request', args: [{ name: 'id', type: 'string', required: true, position: 0 }] },
'cancel-all': { description: 'Cancel all pending requests' },
'auto-approve': { description: 'Auto-approve a tool', args: [{ name: 'tool', type: 'string', required: true, position: 0 }] },
'auto-deny': { description: 'Auto-deny a tool', args: [{ name: 'tool', type: 'string', required: true, position: 0 }] },
interactive: { description: 'Start interactive approval mode' }
},
flags: [
{ name: 'timeout', type: 'integer', description: 'Approval timeout in ms' }
]
},
planning: {
description: 'Task planning and todo management',
subcommands: {
+309 -3
View File
@@ -64,6 +64,35 @@ export {
export * from './tools/arxivTools';
export * from './tools/mcpSse';
// AI SDK Tools Registry
export { tools, registerBuiltinTools } from './tools/tools';
export { getToolsRegistry, createToolsRegistry, resetToolsRegistry, ToolsRegistry } from './tools/registry';
export type {
ToolExecutionContext, ToolLimits, RedactionHooks, ToolLogger,
ToolCapabilities, InstallHints, ToolMetadata, ToolExecutionResult,
PraisonTool, ToolParameterSchema, ToolParameterProperty,
ToolMiddleware, ToolHooks, ToolFactory, RegisteredTool, ToolInstallStatus
} from './tools/registry';
export { MissingDependencyError, MissingEnvVarError } from './tools/registry';
export {
createLoggingMiddleware, createTimeoutMiddleware, createRedactionMiddleware,
createRateLimitMiddleware, createRetryMiddleware, createTracingMiddleware,
createValidationMiddleware, composeMiddleware
} from './tools/registry';
// Built-in tool factories
export {
codeExecution, tavilySearch, tavilyExtract, tavilyCrawl,
exaSearch, perplexitySearch, parallelSearch,
firecrawlScrape, firecrawlCrawl,
superagentGuard, superagentRedact, superagentVerify,
valyuWebSearch, valyuFinanceSearch, valyuPaperSearch, valyuBioSearch,
valyuPatentSearch, valyuSecSearch, valyuEconomicsSearch, valyuCompanyResearch,
bedrockCodeInterpreter, bedrockBrowserNavigate, bedrockBrowserClick, bedrockBrowserFill,
airweaveSearch, codeMode,
registerCustomTool, createCustomTool, registerNpmTool, registerLocalTool
} from './tools/builtins';
// ============================================================================
// SESSION & MEMORY
// ============================================================================
@@ -100,8 +129,7 @@ export { ContextAgent, createContextAgent, type ContextAgentConfig, type Context
// Export evaluation framework
export { accuracyEval, performanceEval, reliabilityEval, EvalSuite, type EvalResult, type PerformanceResult, type AccuracyEvalConfig, type PerformanceEvalConfig, type ReliabilityEvalConfig } from './eval';
// Export observability
export { MemoryObservabilityAdapter, ConsoleObservabilityAdapter, setObservabilityAdapter, getObservabilityAdapter, type ObservabilityAdapter, type TraceContext, type SpanContext, type SpanData, type TraceData } from './observability';
// Note: Observability exports are at the bottom of this file with the full 14+ integrations
// Export skills
export { SkillManager, createSkillManager, parseSkillFile, type Skill, type SkillMetadata, type SkillDiscoveryOptions } from './skills';
@@ -267,6 +295,55 @@ export {
type ToolDefinition as ProviderToolDefinition,
} from './llm/providers';
// Export Observability (14+ integrations)
export {
// Types
type SpanKind,
type SpanStatus,
type SpanData,
type SpanEvent,
type TraceData,
type TraceContext,
type SpanContext,
type ObservabilityAdapter,
type AttributionContext,
type ProviderMetadata,
type ObservabilityToolConfig,
type ObservabilityToolName,
type ObservabilityToolInfo,
// Constants
OBSERVABILITY_TOOLS,
getObservabilityToolInfo,
listObservabilityTools,
hasObservabilityToolEnvVar,
// Adapters
NoopObservabilityAdapter,
noopAdapter,
MemoryObservabilityAdapter,
createMemoryAdapter,
ConsoleObservabilityAdapter,
createConsoleAdapter,
createObservabilityAdapter,
clearAdapterCache,
// Global adapter management
setObservabilityAdapter,
getObservabilityAdapter,
resetObservabilityAdapter,
trace,
} from './observability';
// Export AI SDK Provider Types (60+ providers)
export {
AISDK_PROVIDERS,
PROVIDER_ALIASES,
COMMUNITY_PROVIDERS,
ADAPTERS,
type ProviderInfo,
type ProviderModalities,
type CommunityProvider,
type AdapterInfo,
} from './llm/providers/ai-sdk/types';
// Export CLI Features
export {
// Slash Commands
@@ -318,4 +395,233 @@ export {
// Fast Context
FastContext, createFastContext, getQuickContext,
type FastContextConfig, type ContextSource, type FastContextResult
} from './cli/features';
} from './cli/features';
// ============================================================================
// AI SDK WRAPPER MODULE - Core execution primitives
// ============================================================================
export {
// Text generation (renamed to avoid conflicts with provider exports)
generateText as aiGenerateText,
streamText as aiStreamText,
type GenerateTextOptions as AIGenerateTextOptions,
type GenerateTextResult as AIGenerateTextResult,
type StreamTextOptions as AIStreamTextOptions,
type StreamTextResult as AIStreamTextResult,
type TextStreamPart,
// Object generation
generateObject as aiGenerateObject,
streamObject as aiStreamObject,
type GenerateObjectOptions as AIGenerateObjectOptions,
type GenerateObjectResult as AIGenerateObjectResult,
type StreamObjectOptions as AIStreamObjectOptions,
type StreamObjectResult as AIStreamObjectResult,
// Image generation
generateImage as aiGenerateImage,
type GenerateImageOptions as AIGenerateImageOptions,
type GenerateImageResult as AIGenerateImageResult,
// Embeddings (renamed to avoid conflicts)
embed as aiEmbed,
embedMany as aiEmbedMany,
type EmbedOptions as AIEmbedOptions,
type EmbedResult as AIEmbedResult,
type EmbedManyResult as AIEmbedManyResult,
// Tools
defineTool,
createToolSet,
functionToTool,
type ToolDefinition as AIToolDefinition,
type ToolExecuteFunction,
type ToolInput,
type ToolOutput,
// Models
createModel,
getModel,
parseModel,
MODEL_ALIASES,
listModelAliases,
hasModelAlias,
resolveModelAlias,
type ModelConfig,
type ModelId,
// Middleware (renamed to avoid conflicts)
createCachingMiddleware,
createLoggingMiddleware as createAILoggingMiddleware,
wrapModel,
applyMiddleware,
clearCache as clearAICache,
getCacheStats as getAICacheStats,
type Middleware as AIMiddleware,
type MiddlewareConfig as AIMiddlewareConfig,
type MiddlewareRequest,
type MiddlewareResponse,
// Multimodal
createImagePart,
createFilePart,
createPdfPart,
createTextPart,
createMultimodalMessage,
toMessageContent,
base64ToUint8Array,
uint8ArrayToBase64,
isUrl,
isDataUrl,
type InputPart,
type ImagePart as AIImagePart,
type FilePart as AIFilePart,
type PdfPart,
type TextPart as AITextPart,
// MCP
createMCP,
getMCPClient,
closeMCPClient,
closeAllMCPClients,
mcpToolsToAITools,
type MCPConfig,
type MCPClient,
type MCPTool,
type MCPResource,
type MCPPrompt,
// Server adapters
createHttpHandler,
createExpressHandler,
createHonoHandler,
createFastifyHandler,
createNestHandler,
type ServerHandler,
type ServerHandlerConfig,
// Next.js
createRouteHandler,
createPagesHandler,
type RouteHandlerConfig,
type UseChatConfig,
// Agent loop
createAgentLoop,
AgentLoop,
stopAfterSteps,
stopWhenNoToolCalls,
stopWhen,
type AgentLoopConfig,
type AgentStep as AIAgentStep,
type AgentLoopResult,
type StopCondition,
// UI Message (AI SDK v6 parity)
convertToModelMessages,
convertToUIMessages,
validateUIMessages,
safeValidateUIMessages,
createTextMessage,
createSystemMessage,
hasPendingApprovals,
getToolsNeedingApproval,
createApprovalResponse,
toUIMessageStreamResponse,
pipeUIMessageStreamToResponse,
type UIMessage,
type UIMessagePart,
type TextUIPart,
type ReasoningUIPart,
type ToolUIPart,
type FileUIPart,
type DataUIPart,
type ModelMessage as AIModelMessage,
type UIMessageStreamOptions,
// Tool Approval (AI SDK v6 parity)
ApprovalManager,
getApprovalManager,
setApprovalManager,
withApproval,
ToolApprovalDeniedError,
ToolApprovalTimeoutError,
DANGEROUS_PATTERNS,
isDangerous,
createDangerousPatternChecker,
type ToolApprovalConfig,
type ToolApprovalRequest,
type ToolApprovalResponse,
type ApprovalState,
type ApprovalHandler,
// Speech & Transcription
generateSpeech,
transcribe,
SPEECH_MODELS,
TRANSCRIPTION_MODELS,
type GenerateSpeechOptions,
type GenerateSpeechResult,
type TranscribeOptions,
type TranscribeResult,
type TranscriptionSegment,
// DevTools
enableDevTools,
disableDevTools,
isDevToolsEnabled,
getDevToolsState,
getDevToolsUrl,
createDevToolsMiddleware,
autoEnableDevTools,
type DevToolsConfig,
type DevToolsState,
// Telemetry (AI SDK v6 parity)
configureTelemetry,
getTelemetrySettings,
enableAITelemetry,
disableAITelemetry,
isTelemetryEnabled,
initOpenTelemetry,
getTracer,
createAISpan,
withSpan,
createTelemetryMiddleware,
recordEvent,
getEvents,
clearEvents,
createTelemetrySettings,
type TelemetrySettings as AITelemetrySettings,
type Tracer as AITracer,
type Span as AISpan,
type SpanOptions as AISpanOptions,
type SpanKind as AISpanKind,
type SpanStatus as AISpanStatus,
type TelemetryEvent as AITelemetryEvent,
// OAuth for MCP
type OAuthClientProvider,
} from './ai';
// ============================================================================
// INTEGRATIONS - Slack, Postgres, Computer Use
// ============================================================================
export {
// Slack
createSlackBot,
SlackBot,
verifySlackSignature,
parseSlackMessage,
type SlackConfig,
type SlackMessage,
type SlackResponse,
type SlackEventHandler,
} from './integrations/slack';
export {
// Natural Language Postgres
createNLPostgres,
NLPostgresClient,
createPostgresTool,
type PostgresConfig as NLPostgresConfig,
type TableSchema,
type ColumnSchema,
type QueryResult,
type NLQueryResult,
} from './integrations/postgres';
export {
// Computer Use
createComputerUse,
ComputerUseClient,
createComputerUseAgent,
createCLIApprovalPrompt,
type ComputerUseConfig,
type ComputerUseTools,
type ComputerAction,
type ScreenshotResult,
} from './integrations/computer-use';
@@ -0,0 +1,403 @@
/**
* Computer Use Integration
*
* Provides interfaces for computer use capabilities (browser/desktop control).
* By default, tools are safe no-ops that require explicit implementation.
*/
export interface ComputerUseConfig {
/** Enable browser control */
browser?: boolean;
/** Enable desktop control */
desktop?: boolean;
/** Require human approval for actions (default: true) */
requireApproval?: boolean;
/** Screenshot directory */
screenshotDir?: string;
/** Action timeout in ms (default: 30000) */
timeout?: number;
/** Custom tool implementations */
tools?: Partial<ComputerUseTools>;
}
export interface ComputerUseTools {
/** Take a screenshot */
screenshot: () => Promise<ScreenshotResult>;
/** Click at coordinates */
click: (x: number, y: number, button?: 'left' | 'right' | 'middle') => Promise<void>;
/** Double click at coordinates */
doubleClick: (x: number, y: number) => Promise<void>;
/** Type text */
type: (text: string) => Promise<void>;
/** Press a key or key combination */
key: (key: string) => Promise<void>;
/** Move mouse to coordinates */
moveMouse: (x: number, y: number) => Promise<void>;
/** Scroll */
scroll: (direction: 'up' | 'down' | 'left' | 'right', amount?: number) => Promise<void>;
/** Get screen size */
getScreenSize: () => Promise<{ width: number; height: number }>;
/** Wait for a duration */
wait: (ms: number) => Promise<void>;
/** Execute a shell command (with approval) */
execute: (command: string) => Promise<string>;
}
export interface ScreenshotResult {
/** Base64-encoded image */
base64: string;
/** Image width */
width: number;
/** Image height */
height: number;
/** File path if saved */
path?: string;
}
export interface ComputerAction {
type: 'screenshot' | 'click' | 'doubleClick' | 'type' | 'key' | 'moveMouse' | 'scroll' | 'wait' | 'execute';
params?: any;
requiresApproval?: boolean;
}
/**
* Create a Computer Use agent with safe defaults.
*
* @example Basic usage (safe mode - no-op tools)
* ```typescript
* import { createComputerUse } from 'praisonai/integrations/computer-use';
*
* const computer = createComputerUse({
* requireApproval: true
* });
*
* // Get tools for use with an agent
* const tools = computer.getTools();
* ```
*
* @example With Playwright browser control
* ```typescript
* import { chromium } from 'playwright';
* import { createComputerUse } from 'praisonai/integrations/computer-use';
*
* const browser = await chromium.launch();
* const page = await browser.newPage();
*
* const computer = createComputerUse({
* browser: true,
* tools: {
* screenshot: async () => {
* const buffer = await page.screenshot();
* return {
* base64: buffer.toString('base64'),
* width: 1920,
* height: 1080
* };
* },
* click: async (x, y) => {
* await page.mouse.click(x, y);
* },
* type: async (text) => {
* await page.keyboard.type(text);
* }
* }
* });
* ```
*/
export function createComputerUse(config: ComputerUseConfig = {}): ComputerUseClient {
return new ComputerUseClient(config);
}
export class ComputerUseClient {
private config: ComputerUseConfig;
private tools: ComputerUseTools;
private approvalCallback?: (action: ComputerAction) => Promise<boolean>;
constructor(config: ComputerUseConfig = {}) {
this.config = {
requireApproval: true,
timeout: 30000,
...config,
};
// Initialize with safe no-op tools
this.tools = {
screenshot: config.tools?.screenshot || this.noOpScreenshot,
click: config.tools?.click || this.noOpAction,
doubleClick: config.tools?.doubleClick || this.noOpAction,
type: config.tools?.type || this.noOpAction,
key: config.tools?.key || this.noOpAction,
moveMouse: config.tools?.moveMouse || this.noOpAction,
scroll: config.tools?.scroll || this.noOpAction,
getScreenSize: config.tools?.getScreenSize || (async () => ({ width: 1920, height: 1080 })),
wait: config.tools?.wait || (async (ms: number) => new Promise(r => setTimeout(r, ms))),
execute: config.tools?.execute || this.noOpExecute,
};
}
/**
* Set the approval callback for actions.
*/
onApproval(callback: (action: ComputerAction) => Promise<boolean>): this {
this.approvalCallback = callback;
return this;
}
/**
* No-op screenshot implementation.
*/
private async noOpScreenshot(): Promise<ScreenshotResult> {
console.warn('Computer Use: screenshot() called but no implementation provided');
return {
base64: '',
width: 0,
height: 0,
};
}
/**
* No-op action implementation.
*/
private async noOpAction(...args: any[]): Promise<void> {
console.warn('Computer Use: action called but no implementation provided', args);
}
/**
* No-op execute implementation.
*/
private async noOpExecute(command: string): Promise<string> {
console.warn('Computer Use: execute() called but no implementation provided');
return `[No-op] Would execute: ${command}`;
}
/**
* Request approval for an action.
*/
private async requestApproval(action: ComputerAction): Promise<boolean> {
if (!this.config.requireApproval) {
return true;
}
if (this.approvalCallback) {
return await this.approvalCallback(action);
}
// Default: deny if no callback
console.warn('Computer Use: Action requires approval but no callback set', action);
return false;
}
/**
* Take a screenshot.
*/
async screenshot(): Promise<ScreenshotResult> {
const action: ComputerAction = { type: 'screenshot' };
if (!(await this.requestApproval(action))) {
throw new Error('Screenshot action denied');
}
return await this.tools.screenshot();
}
/**
* Click at coordinates.
*/
async click(x: number, y: number, button: 'left' | 'right' | 'middle' = 'left'): Promise<void> {
const action: ComputerAction = { type: 'click', params: { x, y, button }, requiresApproval: true };
if (!(await this.requestApproval(action))) {
throw new Error('Click action denied');
}
await this.tools.click(x, y, button);
}
/**
* Type text.
*/
async type(text: string): Promise<void> {
const action: ComputerAction = { type: 'type', params: { text }, requiresApproval: true };
if (!(await this.requestApproval(action))) {
throw new Error('Type action denied');
}
await this.tools.type(text);
}
/**
* Press a key.
*/
async key(key: string): Promise<void> {
const action: ComputerAction = { type: 'key', params: { key }, requiresApproval: true };
if (!(await this.requestApproval(action))) {
throw new Error('Key action denied');
}
await this.tools.key(key);
}
/**
* Execute a command.
*/
async execute(command: string): Promise<string> {
const action: ComputerAction = { type: 'execute', params: { command }, requiresApproval: true };
if (!(await this.requestApproval(action))) {
throw new Error('Execute action denied');
}
return await this.tools.execute(command);
}
/**
* Get tools for use with an agent.
*/
getTools(): Record<string, any> {
return {
computer_screenshot: {
description: 'Take a screenshot of the current screen',
parameters: { type: 'object', properties: {} },
execute: async () => {
const result = await this.screenshot();
return JSON.stringify({ width: result.width, height: result.height, hasImage: !!result.base64 });
},
},
computer_click: {
description: 'Click at specific coordinates on the screen',
parameters: {
type: 'object',
properties: {
x: { type: 'number', description: 'X coordinate' },
y: { type: 'number', description: 'Y coordinate' },
button: { type: 'string', enum: ['left', 'right', 'middle'], description: 'Mouse button' },
},
required: ['x', 'y'],
},
execute: async ({ x, y, button }: { x: number; y: number; button?: 'left' | 'right' | 'middle' }) => {
await this.click(x, y, button);
return 'Clicked successfully';
},
},
computer_type: {
description: 'Type text using the keyboard',
parameters: {
type: 'object',
properties: {
text: { type: 'string', description: 'Text to type' },
},
required: ['text'],
},
execute: async ({ text }: { text: string }) => {
await this.type(text);
return 'Typed successfully';
},
},
computer_key: {
description: 'Press a key or key combination (e.g., "Enter", "Control+C")',
parameters: {
type: 'object',
properties: {
key: { type: 'string', description: 'Key or key combination to press' },
},
required: ['key'],
},
execute: async ({ key }: { key: string }) => {
await this.key(key);
return 'Key pressed successfully';
},
},
computer_scroll: {
description: 'Scroll the screen',
parameters: {
type: 'object',
properties: {
direction: { type: 'string', enum: ['up', 'down', 'left', 'right'] },
amount: { type: 'number', description: 'Scroll amount in pixels' },
},
required: ['direction'],
},
execute: async ({ direction, amount }: { direction: 'up' | 'down' | 'left' | 'right'; amount?: number }) => {
const action: ComputerAction = { type: 'scroll', params: { direction, amount }, requiresApproval: true };
if (!(await this.requestApproval(action))) {
throw new Error('Scroll action denied');
}
await this.tools.scroll(direction, amount);
return 'Scrolled successfully';
},
},
computer_execute: {
description: 'Execute a shell command (requires approval)',
parameters: {
type: 'object',
properties: {
command: { type: 'string', description: 'Shell command to execute' },
},
required: ['command'],
},
execute: async ({ command }: { command: string }) => {
const result = await this.execute(command);
return result;
},
},
};
}
}
/**
* Create a simple approval prompt for CLI usage.
*/
export function createCLIApprovalPrompt(): (action: ComputerAction) => Promise<boolean> {
return async (action: ComputerAction): Promise<boolean> => {
const readline = await import('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
const actionDesc = `${action.type}${action.params ? `: ${JSON.stringify(action.params)}` : ''}`;
rl.question(`\n⚠️ Approve action: ${actionDesc}? (y/n) `, (answer) => {
rl.close();
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
});
});
};
}
/**
* Create a Computer Use agent with human-in-the-loop approval.
*
* @example
* ```typescript
* import { Agent } from 'praisonai';
* import { createComputerUseAgent } from 'praisonai/integrations/computer-use';
*
* const agent = await createComputerUseAgent({
* instructions: 'You can control the computer to help the user',
* approvalMode: 'cli' // or 'auto' for no approval
* });
*
* await agent.chat('Open a browser and search for AI news');
* ```
*/
export async function createComputerUseAgent(options: {
instructions?: string;
model?: string;
approvalMode?: 'cli' | 'auto' | 'custom';
onApproval?: (action: ComputerAction) => Promise<boolean>;
tools?: Partial<ComputerUseTools>;
}): Promise<any> {
const { Agent } = await import('../agent/simple');
const computer = createComputerUse({
requireApproval: options.approvalMode !== 'auto',
tools: options.tools,
});
if (options.approvalMode === 'cli') {
computer.onApproval(createCLIApprovalPrompt());
} else if (options.approvalMode === 'custom' && options.onApproval) {
computer.onApproval(options.onApproval);
}
const computerTools = computer.getTools();
return new Agent({
instructions: options.instructions || 'You are a computer use assistant. You can take screenshots, click, type, and execute commands to help the user.',
llm: options.model || 'gpt-4o',
tools: Object.values(computerTools),
});
}
@@ -0,0 +1,461 @@
/**
* Natural Language Postgres
*
* Provides NL->SQL capabilities for querying PostgreSQL databases.
*/
export interface PostgresConfig {
/** Database connection URL */
connectionUrl: string;
/** Schema to use (default: public) */
schema?: string;
/** Read-only mode (default: true for safety) */
readOnly?: boolean;
/** Allowed tables (whitelist) */
allowedTables?: string[];
/** Blocked tables (blacklist) */
blockedTables?: string[];
/** Maximum rows to return (default: 100) */
maxRows?: number;
/** Query timeout in ms (default: 30000) */
timeout?: number;
}
export interface TableSchema {
name: string;
columns: ColumnSchema[];
primaryKey?: string[];
foreignKeys?: ForeignKey[];
}
export interface ColumnSchema {
name: string;
type: string;
nullable: boolean;
defaultValue?: string;
description?: string;
}
export interface ForeignKey {
columns: string[];
referencedTable: string;
referencedColumns: string[];
}
export interface QueryResult {
rows: any[];
rowCount: number;
fields: { name: string; type: string }[];
executionTime: number;
}
export interface NLQueryResult {
/** Natural language query */
query: string;
/** Generated SQL */
sql: string;
/** Query result */
result: QueryResult;
/** Explanation of the query */
explanation?: string;
}
/**
* Create a Natural Language Postgres client.
*
* @example Basic usage
* ```typescript
* import { createNLPostgres } from 'praisonai/integrations/postgres';
*
* const db = await createNLPostgres({
* connectionUrl: process.env.DATABASE_URL!,
* readOnly: true
* });
*
* // Query with natural language
* const result = await db.query('Show me all users who signed up last month');
* console.log(result.rows);
* ```
*
* @example With schema introspection
* ```typescript
* const db = await createNLPostgres({ connectionUrl: '...' });
*
* // Get schema information
* const schema = await db.getSchema();
* console.log('Tables:', schema.map(t => t.name));
*
* // Query with context
* const result = await db.query('How many orders were placed today?');
* ```
*/
export async function createNLPostgres(config: PostgresConfig): Promise<NLPostgresClient> {
const client = new NLPostgresClient(config);
await client.connect();
return client;
}
export class NLPostgresClient {
private config: PostgresConfig;
private pool: any = null;
private schema: TableSchema[] = [];
private connected = false;
constructor(config: PostgresConfig) {
this.config = {
schema: 'public',
readOnly: true,
maxRows: 100,
timeout: 30000,
...config,
};
}
/**
* Connect to the database.
*/
async connect(): Promise<void> {
if (this.connected) return;
try {
// @ts-ignore - Optional dependency
const { Pool } = await import('pg');
this.pool = new Pool({
connectionString: this.config.connectionUrl,
statement_timeout: this.config.timeout,
});
// Test connection
await this.pool.query('SELECT 1');
this.connected = true;
// Load schema
await this.loadSchema();
} catch (error: any) {
throw new Error(
`Failed to connect to PostgreSQL: ${error.message}. ` +
'Install with: npm install pg'
);
}
}
/**
* Disconnect from the database.
*/
async disconnect(): Promise<void> {
if (this.pool) {
await this.pool.end();
this.connected = false;
}
}
/**
* Load database schema.
*/
private async loadSchema(): Promise<void> {
const schemaQuery = `
SELECT
t.table_name,
c.column_name,
c.data_type,
c.is_nullable,
c.column_default,
pgd.description
FROM information_schema.tables t
JOIN information_schema.columns c
ON t.table_name = c.table_name
AND t.table_schema = c.table_schema
LEFT JOIN pg_catalog.pg_statio_all_tables st
ON st.relname = t.table_name
LEFT JOIN pg_catalog.pg_description pgd
ON pgd.objoid = st.relid
AND pgd.objsubid = c.ordinal_position
WHERE t.table_schema = $1
AND t.table_type = 'BASE TABLE'
ORDER BY t.table_name, c.ordinal_position
`;
const result = await this.pool.query(schemaQuery, [this.config.schema]);
const tableMap = new Map<string, TableSchema>();
for (const row of result.rows) {
// Check allowed/blocked tables
if (this.config.allowedTables && !this.config.allowedTables.includes(row.table_name)) {
continue;
}
if (this.config.blockedTables && this.config.blockedTables.includes(row.table_name)) {
continue;
}
if (!tableMap.has(row.table_name)) {
tableMap.set(row.table_name, {
name: row.table_name,
columns: [],
});
}
const table = tableMap.get(row.table_name)!;
table.columns.push({
name: row.column_name,
type: row.data_type,
nullable: row.is_nullable === 'YES',
defaultValue: row.column_default,
description: row.description,
});
}
this.schema = Array.from(tableMap.values());
}
/**
* Get the database schema.
*/
getSchema(): TableSchema[] {
return this.schema;
}
/**
* Get schema as a string for LLM context.
*/
getSchemaContext(): string {
let context = 'Database Schema:\n\n';
for (const table of this.schema) {
context += `Table: ${table.name}\n`;
context += 'Columns:\n';
for (const col of table.columns) {
context += ` - ${col.name} (${col.type}${col.nullable ? ', nullable' : ''})`;
if (col.description) {
context += ` - ${col.description}`;
}
context += '\n';
}
context += '\n';
}
return context;
}
/**
* Execute a raw SQL query.
*/
async executeSQL(sql: string): Promise<QueryResult> {
if (!this.connected) {
throw new Error('Not connected to database');
}
// Safety check for read-only mode
if (this.config.readOnly) {
const normalizedSQL = sql.trim().toLowerCase();
const writeOperations = ['insert', 'update', 'delete', 'drop', 'alter', 'create', 'truncate'];
for (const op of writeOperations) {
if (normalizedSQL.startsWith(op)) {
throw new Error(`Write operation '${op}' not allowed in read-only mode`);
}
}
}
// Add LIMIT if not present
const hasLimit = /\blimit\s+\d+/i.test(sql);
const finalSQL = hasLimit ? sql : `${sql} LIMIT ${this.config.maxRows}`;
const startTime = Date.now();
const result = await this.pool.query(finalSQL);
const executionTime = Date.now() - startTime;
return {
rows: result.rows,
rowCount: result.rowCount,
fields: result.fields?.map((f: any) => ({ name: f.name, type: f.dataTypeID?.toString() })) || [],
executionTime,
};
}
/**
* Query the database using natural language.
*/
async query(naturalLanguageQuery: string, options?: { model?: string }): Promise<NLQueryResult> {
const model = options?.model || 'gpt-4o-mini';
// Generate SQL from natural language
const { generateText } = await import('../ai/generate-text');
const schemaContext = this.getSchemaContext();
const prompt = `You are a SQL expert. Convert the following natural language query to PostgreSQL SQL.
${schemaContext}
Rules:
1. Only generate SELECT queries (read-only)
2. Use proper PostgreSQL syntax
3. Include appropriate JOINs when needed
4. Add reasonable LIMIT if not specified
5. Return ONLY the SQL query, no explanations
Natural language query: ${naturalLanguageQuery}
SQL:`;
const result = await generateText({
model,
prompt,
temperature: 0,
});
// Extract SQL from response
let sql = result.text.trim();
// Remove markdown code blocks if present
if (sql.startsWith('```')) {
sql = sql.replace(/```sql?\n?/g, '').replace(/```/g, '').trim();
}
// Execute the SQL
const queryResult = await this.executeSQL(sql);
return {
query: naturalLanguageQuery,
sql,
result: queryResult,
};
}
/**
* Chat with the database (conversational interface).
*/
async chat(message: string, options?: { model?: string; history?: Array<{ role: string; content: string }> }): Promise<string> {
const model = options?.model || 'gpt-4o-mini';
const history = options?.history || [];
const { generateText } = await import('../ai/generate-text');
const schemaContext = this.getSchemaContext();
const systemPrompt = `You are a helpful database assistant. You can query a PostgreSQL database to answer questions.
${schemaContext}
When the user asks a question that requires database data:
1. Generate a SQL query to get the data
2. Execute it using the query tool
3. Summarize the results in natural language
Always be helpful and explain your findings clearly.`;
const messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }> = [
{ role: 'system', content: systemPrompt },
...history.map(h => ({ role: h.role as 'user' | 'assistant', content: h.content })),
{ role: 'user', content: message },
];
// Define the query tool
const tools = {
query_database: {
description: 'Execute a SQL query on the PostgreSQL database',
parameters: {
type: 'object',
properties: {
sql: {
type: 'string',
description: 'The SQL query to execute (SELECT only)',
},
},
required: ['sql'],
},
execute: async ({ sql }: { sql: string }) => {
try {
const result = await this.executeSQL(sql);
return JSON.stringify({
rowCount: result.rowCount,
rows: result.rows.slice(0, 10), // Limit for context
hasMore: result.rowCount > 10,
});
} catch (error: any) {
return JSON.stringify({ error: error.message });
}
},
},
};
const result = await generateText({
model,
messages,
tools,
maxSteps: 3,
});
return result.text;
}
/**
* Inspect the database structure.
*/
async inspect(): Promise<{
tables: number;
schema: TableSchema[];
sampleData: Record<string, any[]>;
}> {
const sampleData: Record<string, any[]> = {};
for (const table of this.schema) {
try {
const result = await this.executeSQL(`SELECT * FROM ${table.name} LIMIT 3`);
sampleData[table.name] = result.rows;
} catch {
sampleData[table.name] = [];
}
}
return {
tables: this.schema.length,
schema: this.schema,
sampleData,
};
}
}
/**
* Create a tool for querying Postgres with natural language.
*
* @example Use with an agent
* ```typescript
* import { Agent } from 'praisonai';
* import { createPostgresTool } from 'praisonai/integrations/postgres';
*
* const dbTool = await createPostgresTool({
* connectionUrl: process.env.DATABASE_URL!
* });
*
* const agent = new Agent({
* instructions: 'You can query the database',
* tools: [dbTool]
* });
* ```
*/
export async function createPostgresTool(config: PostgresConfig): Promise<any> {
const client = await createNLPostgres(config);
return {
name: 'query_database',
description: `Query the PostgreSQL database using natural language. Available tables: ${client.getSchema().map(t => t.name).join(', ')}`,
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Natural language query describing what data you want',
},
},
required: ['query'],
},
execute: async ({ query }: { query: string }) => {
const result = await client.query(query);
return JSON.stringify({
sql: result.sql,
rowCount: result.result.rowCount,
rows: result.result.rows,
});
},
};
}
+320
View File
@@ -0,0 +1,320 @@
/**
* Slack Integration
*
* Provides Slack bot adapters for building AI-powered Slack bots.
*/
export interface SlackConfig {
/** Slack Bot Token (xoxb-...) */
botToken: string;
/** Slack App Token for Socket Mode (xapp-...) */
appToken?: string;
/** Slack Signing Secret for webhook verification */
signingSecret?: string;
/** Enable Socket Mode (default: false, uses webhooks) */
socketMode?: boolean;
}
export interface SlackMessage {
/** Channel ID */
channel: string;
/** User ID */
user: string;
/** Message text */
text: string;
/** Thread timestamp (for replies) */
threadTs?: string;
/** Message timestamp */
ts: string;
/** Bot ID (if from a bot) */
botId?: string;
}
export interface SlackResponse {
/** Response text */
text: string;
/** Blocks for rich formatting */
blocks?: any[];
/** Thread timestamp (to reply in thread) */
threadTs?: string;
/** Unfurl links */
unfurlLinks?: boolean;
/** Unfurl media */
unfurlMedia?: boolean;
}
export interface SlackEventHandler {
/** Handle a message event */
onMessage: (message: SlackMessage) => Promise<SlackResponse | null>;
/** Handle an app mention event */
onAppMention?: (message: SlackMessage) => Promise<SlackResponse | null>;
/** Handle a reaction added event */
onReactionAdded?: (event: any) => Promise<void>;
}
/**
* Create a Slack bot adapter.
*
* @example Basic usage
* ```typescript
* import { createSlackBot } from 'praisonai/integrations/slack';
* import { Agent } from 'praisonai';
*
* const agent = new Agent({ instructions: 'You are a helpful Slack bot' });
*
* const bot = createSlackBot({
* botToken: process.env.SLACK_BOT_TOKEN!,
* signingSecret: process.env.SLACK_SIGNING_SECRET!
* });
*
* bot.onMessage(async (message) => {
* const response = await agent.chat(message.text);
* return { text: response, threadTs: message.ts };
* });
*
* // Start webhook server
* bot.listen(3000);
* ```
*
* @example With Socket Mode
* ```typescript
* const bot = createSlackBot({
* botToken: process.env.SLACK_BOT_TOKEN!,
* appToken: process.env.SLACK_APP_TOKEN!,
* socketMode: true
* });
*
* bot.onMessage(async (message) => {
* return { text: 'Hello!' };
* });
*
* await bot.start();
* ```
*/
export function createSlackBot(config: SlackConfig): SlackBot {
return new SlackBot(config);
}
export class SlackBot {
private config: SlackConfig;
private messageHandler?: (message: SlackMessage) => Promise<SlackResponse | null>;
private mentionHandler?: (message: SlackMessage) => Promise<SlackResponse | null>;
private reactionHandler?: (event: any) => Promise<void>;
private boltApp: any = null;
constructor(config: SlackConfig) {
this.config = config;
}
/**
* Set the message handler.
*/
onMessage(handler: (message: SlackMessage) => Promise<SlackResponse | null>): this {
this.messageHandler = handler;
return this;
}
/**
* Set the app mention handler.
*/
onAppMention(handler: (message: SlackMessage) => Promise<SlackResponse | null>): this {
this.mentionHandler = handler;
return this;
}
/**
* Set the reaction handler.
*/
onReactionAdded(handler: (event: any) => Promise<void>): this {
this.reactionHandler = handler;
return this;
}
/**
* Initialize the Bolt app (lazy load).
*/
private async initBolt(): Promise<any> {
if (this.boltApp) return this.boltApp;
try {
// @ts-ignore - Optional dependency
const { App } = await import('@slack/bolt');
this.boltApp = new App({
token: this.config.botToken,
signingSecret: this.config.signingSecret,
appToken: this.config.appToken,
socketMode: this.config.socketMode,
});
// Register message handler
if (this.messageHandler) {
this.boltApp.message(async ({ message, say }: any) => {
// Skip bot messages
if (message.bot_id) return;
const slackMessage: SlackMessage = {
channel: message.channel,
user: message.user,
text: message.text || '',
threadTs: message.thread_ts,
ts: message.ts,
};
const response = await this.messageHandler!(slackMessage);
if (response) {
await say({
text: response.text,
blocks: response.blocks,
thread_ts: response.threadTs || message.ts,
unfurl_links: response.unfurlLinks,
unfurl_media: response.unfurlMedia,
});
}
});
}
// Register app mention handler
if (this.mentionHandler) {
this.boltApp.event('app_mention', async ({ event, say }: any) => {
const slackMessage: SlackMessage = {
channel: event.channel,
user: event.user,
text: event.text || '',
threadTs: event.thread_ts,
ts: event.ts,
};
const response = await this.mentionHandler!(slackMessage);
if (response) {
await say({
text: response.text,
blocks: response.blocks,
thread_ts: response.threadTs || event.ts,
});
}
});
}
// Register reaction handler
if (this.reactionHandler) {
this.boltApp.event('reaction_added', async ({ event }: any) => {
await this.reactionHandler!(event);
});
}
return this.boltApp;
} catch (error: any) {
throw new Error(
`Failed to initialize Slack Bolt: ${error.message}. ` +
'Install with: npm install @slack/bolt'
);
}
}
/**
* Start the bot (Socket Mode).
*/
async start(): Promise<void> {
const app = await this.initBolt();
await app.start();
console.log('⚡️ Slack bot is running in Socket Mode');
}
/**
* Start webhook server.
*/
async listen(port: number = 3000): Promise<void> {
const app = await this.initBolt();
await app.start(port);
console.log(`⚡️ Slack bot is running on port ${port}`);
}
/**
* Stop the bot.
*/
async stop(): Promise<void> {
if (this.boltApp) {
await this.boltApp.stop();
}
}
/**
* Send a message to a channel.
*/
async sendMessage(channel: string, text: string, options?: Partial<SlackResponse>): Promise<void> {
const app = await this.initBolt();
await app.client.chat.postMessage({
token: this.config.botToken,
channel,
text,
blocks: options?.blocks,
thread_ts: options?.threadTs,
unfurl_links: options?.unfurlLinks,
unfurl_media: options?.unfurlMedia,
});
}
/**
* Get an Express middleware for webhook handling.
*/
getExpressMiddleware(): any {
return async (req: any, res: any, next: any) => {
const app = await this.initBolt();
return app.receiver.app(req, res, next);
};
}
}
/**
* Verify Slack request signature.
*/
export function verifySlackSignature(
signingSecret: string,
signature: string,
timestamp: string,
body: string
): boolean {
const crypto = require('crypto');
const baseString = `v0:${timestamp}:${body}`;
const hmac = crypto.createHmac('sha256', signingSecret);
hmac.update(baseString);
const computedSignature = `v0=${hmac.digest('hex')}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(computedSignature)
);
}
/**
* Parse Slack message text to extract mentions and links.
*/
export function parseSlackMessage(text: string): {
mentions: string[];
links: string[];
cleanText: string;
} {
const mentions: string[] = [];
const links: string[] = [];
// Extract user mentions <@U123456>
const mentionRegex = /<@([A-Z0-9]+)>/g;
let match;
while ((match = mentionRegex.exec(text)) !== null) {
mentions.push(match[1]);
}
// Extract links <http://...|label>
const linkRegex = /<(https?:\/\/[^|>]+)(?:\|[^>]+)?>/g;
while ((match = linkRegex.exec(text)) !== null) {
links.push(match[1]);
}
// Clean text (remove mentions and format links)
const cleanText = text
.replace(/<@[A-Z0-9]+>/g, '')
.replace(/<(https?:\/\/[^|>]+)(?:\|([^>]+))?>/g, '$2 ($1)')
.trim();
return { mentions, links, cleanText };
}
@@ -194,34 +194,267 @@ export const SAFE_DEFAULTS = {
} as const;
/**
* Supported AI SDK providers with their package names
* Provider modality support
*/
export const AISDK_PROVIDERS: Record<string, { package: string; envKey: string }> = {
openai: { package: '@ai-sdk/openai', envKey: 'OPENAI_API_KEY' },
anthropic: { package: '@ai-sdk/anthropic', envKey: 'ANTHROPIC_API_KEY' },
google: { package: '@ai-sdk/google', envKey: 'GOOGLE_API_KEY' },
azure: { package: '@ai-sdk/azure', envKey: 'AZURE_API_KEY' },
'amazon-bedrock': { package: '@ai-sdk/amazon-bedrock', envKey: 'AWS_ACCESS_KEY_ID' },
groq: { package: '@ai-sdk/groq', envKey: 'GROQ_API_KEY' },
mistral: { package: '@ai-sdk/mistral', envKey: 'MISTRAL_API_KEY' },
cohere: { package: '@ai-sdk/cohere', envKey: 'COHERE_API_KEY' },
deepseek: { package: '@ai-sdk/deepseek', envKey: 'DEEPSEEK_API_KEY' },
xai: { package: '@ai-sdk/xai', envKey: 'XAI_API_KEY' },
fireworks: { package: '@ai-sdk/fireworks', envKey: 'FIREWORKS_API_KEY' },
togetherai: { package: '@ai-sdk/togetherai', envKey: 'TOGETHER_API_KEY' },
perplexity: { package: '@ai-sdk/perplexity', envKey: 'PERPLEXITY_API_KEY' },
export interface ProviderModalities {
text: boolean;
chat: boolean;
embeddings: boolean;
image: boolean;
audio: boolean;
speech: boolean;
tools: boolean;
}
/**
* Provider info with package, env key, and modalities
*/
export interface ProviderInfo {
package: string;
envKey: string;
modalities: ProviderModalities;
description?: string;
docsUrl?: string;
}
/**
* Default modalities for chat-only providers
*/
const CHAT_ONLY: ProviderModalities = {
text: true, chat: true, embeddings: false, image: false, audio: false, speech: false, tools: true
};
/**
* Full modalities for providers with embeddings
*/
const CHAT_EMBED: ProviderModalities = {
text: true, chat: true, embeddings: true, image: false, audio: false, speech: false, tools: true
};
/**
* Modalities for image generation providers
*/
const IMAGE_GEN: ProviderModalities = {
text: false, chat: false, embeddings: false, image: true, audio: false, speech: false, tools: false
};
/**
* Modalities for audio/speech providers
*/
const AUDIO_SPEECH: ProviderModalities = {
text: false, chat: false, embeddings: false, image: false, audio: true, speech: true, tools: false
};
/**
* Full multimodal support
*/
const MULTIMODAL: ProviderModalities = {
text: true, chat: true, embeddings: true, image: true, audio: true, speech: true, tools: true
};
/**
* Supported AI SDK providers with their package names and capabilities
* Comprehensive list covering all AI SDK v6 providers
*/
export const AISDK_PROVIDERS: Record<string, ProviderInfo> = {
// === Core Providers (Official AI SDK) ===
openai: { package: '@ai-sdk/openai', envKey: 'OPENAI_API_KEY', modalities: MULTIMODAL, description: 'OpenAI GPT models' },
anthropic: { package: '@ai-sdk/anthropic', envKey: 'ANTHROPIC_API_KEY', modalities: { ...CHAT_ONLY, image: true }, description: 'Anthropic Claude models' },
google: { package: '@ai-sdk/google', envKey: 'GOOGLE_API_KEY', modalities: MULTIMODAL, description: 'Google Generative AI (Gemini)' },
'google-vertex': { package: '@ai-sdk/google-vertex', envKey: 'GOOGLE_APPLICATION_CREDENTIALS', modalities: MULTIMODAL, description: 'Google Vertex AI' },
azure: { package: '@ai-sdk/azure', envKey: 'AZURE_API_KEY', modalities: MULTIMODAL, description: 'Azure OpenAI Service' },
'amazon-bedrock': { package: '@ai-sdk/amazon-bedrock', envKey: 'AWS_ACCESS_KEY_ID', modalities: CHAT_EMBED, description: 'Amazon Bedrock' },
// === xAI / Grok ===
xai: { package: '@ai-sdk/xai', envKey: 'XAI_API_KEY', modalities: { ...CHAT_ONLY, image: true }, description: 'xAI Grok models' },
// === Vercel ===
vercel: { package: '@ai-sdk/vercel', envKey: 'VERCEL_API_KEY', modalities: CHAT_ONLY, description: 'Vercel AI models' },
// === Inference Providers ===
groq: { package: '@ai-sdk/groq', envKey: 'GROQ_API_KEY', modalities: CHAT_ONLY, description: 'Groq inference' },
fireworks: { package: '@ai-sdk/fireworks', envKey: 'FIREWORKS_API_KEY', modalities: CHAT_EMBED, description: 'Fireworks AI' },
togetherai: { package: '@ai-sdk/togetherai', envKey: 'TOGETHER_API_KEY', modalities: CHAT_EMBED, description: 'Together.ai' },
deepinfra: { package: '@ai-sdk/deepinfra', envKey: 'DEEPINFRA_API_KEY', modalities: CHAT_EMBED, description: 'DeepInfra' },
replicate: { package: '@ai-sdk/replicate', envKey: 'REPLICATE_API_TOKEN', modalities: { ...CHAT_ONLY, image: true }, description: 'Replicate' },
baseten: { package: '@ai-sdk/baseten', envKey: 'BASETEN_API_KEY', modalities: CHAT_ONLY, description: 'Baseten' },
huggingface: { package: '@ai-sdk/huggingface', envKey: 'HUGGINGFACE_API_KEY', modalities: CHAT_EMBED, description: 'Hugging Face Inference' },
// === Model Providers ===
mistral: { package: '@ai-sdk/mistral', envKey: 'MISTRAL_API_KEY', modalities: CHAT_EMBED, description: 'Mistral AI' },
cohere: { package: '@ai-sdk/cohere', envKey: 'COHERE_API_KEY', modalities: CHAT_EMBED, description: 'Cohere' },
deepseek: { package: '@ai-sdk/deepseek', envKey: 'DEEPSEEK_API_KEY', modalities: CHAT_ONLY, description: 'DeepSeek' },
cerebras: { package: '@ai-sdk/cerebras', envKey: 'CEREBRAS_API_KEY', modalities: CHAT_ONLY, description: 'Cerebras' },
perplexity: { package: '@ai-sdk/perplexity', envKey: 'PERPLEXITY_API_KEY', modalities: CHAT_ONLY, description: 'Perplexity AI' },
// === Image Generation ===
fal: { package: '@ai-sdk/fal', envKey: 'FAL_KEY', modalities: IMAGE_GEN, description: 'Fal.ai image generation' },
'black-forest-labs': { package: '@ai-sdk/black-forest-labs', envKey: 'BFL_API_KEY', modalities: IMAGE_GEN, description: 'Black Forest Labs (FLUX)' },
luma: { package: '@ai-sdk/luma', envKey: 'LUMA_API_KEY', modalities: IMAGE_GEN, description: 'Luma AI video generation' },
// === Audio/Speech Providers ===
elevenlabs: { package: '@ai-sdk/elevenlabs', envKey: 'ELEVENLABS_API_KEY', modalities: AUDIO_SPEECH, description: 'ElevenLabs TTS' },
assemblyai: { package: '@ai-sdk/assemblyai', envKey: 'ASSEMBLYAI_API_KEY', modalities: AUDIO_SPEECH, description: 'AssemblyAI transcription' },
deepgram: { package: '@ai-sdk/deepgram', envKey: 'DEEPGRAM_API_KEY', modalities: AUDIO_SPEECH, description: 'Deepgram speech' },
gladia: { package: '@ai-sdk/gladia', envKey: 'GLADIA_API_KEY', modalities: AUDIO_SPEECH, description: 'Gladia transcription' },
lmnt: { package: '@ai-sdk/lmnt', envKey: 'LMNT_API_KEY', modalities: AUDIO_SPEECH, description: 'LMNT TTS' },
hume: { package: '@ai-sdk/hume', envKey: 'HUME_API_KEY', modalities: AUDIO_SPEECH, description: 'Hume AI emotion' },
revai: { package: '@ai-sdk/revai', envKey: 'REVAI_API_KEY', modalities: AUDIO_SPEECH, description: 'Rev.ai transcription' },
// === Gateway/Proxy Providers ===
'ai-gateway': { package: '@ai-sdk/gateway', envKey: 'AI_GATEWAY_API_KEY', modalities: CHAT_EMBED, description: 'AI Gateway' },
openrouter: { package: '@openrouter/ai-sdk-provider', envKey: 'OPENROUTER_API_KEY', modalities: CHAT_EMBED, description: 'OpenRouter' },
portkey: { package: '@portkey-ai/vercel-provider', envKey: 'PORTKEY_API_KEY', modalities: CHAT_EMBED, description: 'Portkey AI Gateway' },
helicone: { package: '@helicone/ai-sdk', envKey: 'HELICONE_API_KEY', modalities: CHAT_EMBED, description: 'Helicone proxy' },
// === Cloudflare ===
'cloudflare-workers-ai': { package: '@ai-sdk/cloudflare', envKey: 'CLOUDFLARE_API_TOKEN', modalities: CHAT_EMBED, description: 'Cloudflare Workers AI' },
'cloudflare-ai-gateway': { package: '@ai-sdk/cloudflare', envKey: 'CLOUDFLARE_API_TOKEN', modalities: CHAT_EMBED, description: 'Cloudflare AI Gateway' },
// === Local/Self-hosted ===
ollama: { package: 'ollama-ai-provider', envKey: 'OLLAMA_BASE_URL', modalities: CHAT_EMBED, description: 'Ollama local models' },
'lm-studio': { package: '@ai-sdk/openai-compatible', envKey: 'LM_STUDIO_BASE_URL', modalities: CHAT_ONLY, description: 'LM Studio' },
'nvidia-nim': { package: '@ai-sdk/openai-compatible', envKey: 'NVIDIA_API_KEY', modalities: CHAT_EMBED, description: 'NVIDIA NIM' },
// === OpenAI Compatible ===
'openai-compatible': { package: '@ai-sdk/openai-compatible', envKey: 'OPENAI_COMPATIBLE_API_KEY', modalities: CHAT_EMBED, description: 'OpenAI-compatible endpoints' },
// === Regional/Specialized Providers ===
qwen: { package: '@ai-sdk/qwen', envKey: 'DASHSCOPE_API_KEY', modalities: CHAT_EMBED, description: 'Alibaba Qwen' },
'zhipu-ai': { package: '@ai-sdk/zhipu', envKey: 'ZHIPU_API_KEY', modalities: CHAT_EMBED, description: 'Zhipu AI (GLM)' },
minimax: { package: '@ai-sdk/minimax', envKey: 'MINIMAX_API_KEY', modalities: CHAT_ONLY, description: 'MiniMax' },
spark: { package: '@ai-sdk/spark', envKey: 'SPARK_API_KEY', modalities: CHAT_ONLY, description: 'iFlytek Spark' },
sambanova: { package: '@ai-sdk/sambanova', envKey: 'SAMBANOVA_API_KEY', modalities: CHAT_ONLY, description: 'SambaNova' },
// === Embedding Specialists ===
'voyage-ai': { package: '@ai-sdk/voyage', envKey: 'VOYAGE_API_KEY', modalities: { ...CHAT_ONLY, embeddings: true, tools: false }, description: 'Voyage AI embeddings' },
'jina-ai': { package: '@ai-sdk/jina', envKey: 'JINA_API_KEY', modalities: { ...CHAT_ONLY, embeddings: true, tools: false }, description: 'Jina AI embeddings' },
mixedbread: { package: '@ai-sdk/mixedbread', envKey: 'MIXEDBREAD_API_KEY', modalities: { ...CHAT_ONLY, embeddings: true, tools: false }, description: 'Mixedbread embeddings' },
// === Memory/Agent Providers ===
mem0: { package: '@mem0ai/vercel-ai-provider', envKey: 'MEM0_API_KEY', modalities: CHAT_ONLY, description: 'Mem0 memory layer' },
letta: { package: '@letta-ai/vercel-ai-provider', envKey: 'LETTA_API_KEY', modalities: CHAT_ONLY, description: 'Letta agent memory' },
// === Enterprise/Cloud ===
'azure-ai': { package: '@ai-sdk/azure', envKey: 'AZURE_API_KEY', modalities: CHAT_EMBED, description: 'Azure AI Services' },
'sap-ai-core': { package: '@sap-ai-sdk/ai-core', envKey: 'SAP_AI_CORE_KEY', modalities: CHAT_ONLY, description: 'SAP AI Core' },
// === Other Providers ===
friendliai: { package: '@friendliai/ai-provider', envKey: 'FRIENDLI_TOKEN', modalities: CHAT_ONLY, description: 'FriendliAI' },
runpod: { package: '@runpod/ai-sdk', envKey: 'RUNPOD_API_KEY', modalities: CHAT_ONLY, description: 'RunPod serverless' },
clarifai: { package: '@clarifai/ai-sdk', envKey: 'CLARIFAI_PAT', modalities: { ...CHAT_ONLY, image: true }, description: 'Clarifai' },
'inflection-ai': { package: '@ai-sdk/inflection', envKey: 'INFLECTION_API_KEY', modalities: CHAT_ONLY, description: 'Inflection AI (Pi)' },
dify: { package: '@dify-ai/ai-sdk', envKey: 'DIFY_API_KEY', modalities: CHAT_ONLY, description: 'Dify platform' },
sarvam: { package: '@sarvam-ai/ai-sdk', envKey: 'SARVAM_API_KEY', modalities: CHAT_ONLY, description: 'Sarvam AI (Indic)' },
'aiml-api': { package: '@aimlapi/ai-sdk', envKey: 'AIML_API_KEY', modalities: CHAT_EMBED, description: 'AI/ML API' },
aihubmix: { package: '@aihubmix/ai-sdk', envKey: 'AIHUBMIX_API_KEY', modalities: CHAT_ONLY, description: 'AIHubMix' },
crosshatch: { package: '@crosshatch/ai-sdk', envKey: 'CROSSHATCH_API_KEY', modalities: CHAT_ONLY, description: 'Crosshatch' },
langdb: { package: '@langdb/ai-sdk', envKey: 'LANGDB_API_KEY', modalities: CHAT_ONLY, description: 'LangDB' },
requesty: { package: '@requesty/ai-sdk', envKey: 'REQUESTY_API_KEY', modalities: CHAT_ONLY, description: 'Requesty' },
// === Anthropic Vertex ===
'anthropic-vertex': { package: '@anthropic-ai/vertex-sdk', envKey: 'GOOGLE_APPLICATION_CREDENTIALS', modalities: { ...CHAT_ONLY, image: true }, description: 'Anthropic via Vertex AI' },
// === Heroku ===
heroku: { package: '@heroku/ai-sdk', envKey: 'HEROKU_API_KEY', modalities: CHAT_ONLY, description: 'Heroku AI' },
} as const;
/**
* Provider aliases for convenience
*/
export const PROVIDER_ALIASES: Record<string, string> = {
// Core aliases
oai: 'openai',
claude: 'anthropic',
gemini: 'google',
gcp: 'google',
vertex: 'google-vertex',
'vertex-ai': 'google-vertex',
// Cloud aliases
aws: 'amazon-bedrock',
bedrock: 'amazon-bedrock',
'azure-openai': 'azure',
// Inference aliases
together: 'togetherai',
'together-ai': 'togetherai',
pplx: 'perplexity',
grok: 'xai',
// Image aliases
flux: 'black-forest-labs',
bfl: 'black-forest-labs',
// Audio aliases
'11labs': 'elevenlabs',
// Gateway aliases
'cf-ai': 'cloudflare-workers-ai',
'cf-gateway': 'cloudflare-ai-gateway',
cloudflare: 'cloudflare-workers-ai',
// Local aliases
local: 'ollama',
lmstudio: 'lm-studio',
nim: 'nvidia-nim',
nvidia: 'nvidia-nim',
// Regional aliases
alibaba: 'qwen',
dashscope: 'qwen',
glm: 'zhipu-ai',
zhipu: 'zhipu-ai',
iflytek: 'spark',
// Other aliases
voyage: 'voyage-ai',
jina: 'jina-ai',
'ai-ml': 'aiml-api',
inflection: 'inflection-ai',
pi: 'inflection-ai',
} as const;
/**
* Community provider registry for external/third-party providers
* These are not bundled but can be registered at runtime
*/
export interface CommunityProvider {
name: string;
package: string;
envKey: string;
description: string;
maintainer?: string;
repository?: string;
}
/**
* Known community providers (not bundled, user must install)
*/
export const COMMUNITY_PROVIDERS: CommunityProvider[] = [
{ name: 'a2a', package: '@anthropic/a2a-sdk', envKey: 'A2A_API_KEY', description: 'Anthropic A2A protocol' },
{ name: 'acp', package: '@acp/ai-sdk', envKey: 'ACP_API_KEY', description: 'Agent Client Protocol' },
{ name: 'firemoon', package: '@firemoon/ai-sdk', envKey: 'FIREMOON_API_KEY', description: 'Firemoon AI' },
{ name: 'supermemory', package: '@supermemory/ai-sdk', envKey: 'SUPERMEMORY_API_KEY', description: 'Supermemory' },
{ name: 'react-native-apple', package: '@react-native-ai/apple', envKey: '', description: 'React Native Apple AI' },
{ name: 'claude-code', package: '@anthropic/claude-code', envKey: 'ANTHROPIC_API_KEY', description: 'Claude Code CLI' },
{ name: 'gemini-cli', package: '@google/gemini-cli', envKey: 'GOOGLE_API_KEY', description: 'Gemini CLI' },
{ name: 'built-in-ai', package: '@anthropic/built-in-ai', envKey: '', description: 'Browser built-in AI' },
{ name: 'mcp-sampling', package: '@modelcontextprotocol/sampling', envKey: '', description: 'MCP Sampling Provider' },
{ name: 'automatic1111', package: '@a1111/ai-sdk', envKey: 'A1111_BASE_URL', description: 'Automatic1111 Stable Diffusion' },
];
/**
* Adapter ecosystems (optional integrations)
*/
export interface AdapterInfo {
name: string;
package: string;
description: string;
type: 'langchain' | 'llamaindex' | 'other';
}
export const ADAPTERS: AdapterInfo[] = [
{ name: 'langchain', package: '@ai-sdk/langchain', description: 'LangChain adapter', type: 'langchain' },
{ name: 'llamaindex', package: '@ai-sdk/llamaindex', description: 'LlamaIndex adapter', type: 'llamaindex' },
];
@@ -0,0 +1,107 @@
/**
* Console Observability Adapter
* Logs traces and spans to console for debugging
*/
import type {
ObservabilityAdapter,
TraceContext,
SpanContext,
SpanKind,
SpanStatus,
AttributionContext,
ProviderMetadata
} from '../types';
import { MemoryObservabilityAdapter } from './memory';
/**
* Console Observability Adapter
* Wraps memory adapter and logs all operations to console
*/
export class ConsoleObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'console';
readonly isEnabled = true;
private memory: MemoryObservabilityAdapter;
private verbose: boolean;
constructor(options: { verbose?: boolean } = {}) {
this.memory = new MemoryObservabilityAdapter();
this.verbose = options.verbose ?? false;
}
async initialize(): Promise<void> {
console.log('[OBSERVABILITY] Console adapter initialized');
}
async shutdown(): Promise<void> {
console.log('[OBSERVABILITY] Console adapter shutdown');
await this.memory.shutdown();
}
startTrace(
name: string,
metadata?: Record<string, unknown>,
attribution?: AttributionContext
): TraceContext {
const ctx = this.memory.startTrace(name, metadata, attribution);
console.log(`[TRACE START] ${name}`, this.verbose ? { traceId: ctx.traceId, metadata, attribution } : '');
return ctx;
}
endTrace(traceId: string, status?: SpanStatus): void {
console.log(`[TRACE END] ${traceId} - ${status || 'completed'}`);
this.memory.endTrace(traceId, status);
}
startSpan(
traceId: string,
name: string,
kind: SpanKind,
parentId?: string
): SpanContext {
const ctx = this.memory.startSpan(traceId, name, kind, parentId);
console.log(` [SPAN START] ${name} (${kind})`, this.verbose ? { spanId: ctx.spanId, parentId } : '');
return ctx;
}
endSpan(
spanId: string,
status?: SpanStatus,
attributes?: Record<string, unknown>
): void {
console.log(` [SPAN END] ${spanId} - ${status || 'completed'}`, this.verbose && attributes ? attributes : '');
this.memory.endSpan(spanId, status, attributes);
}
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void {
console.log(` [EVENT] ${name}`, this.verbose && attributes ? attributes : '');
this.memory.addEvent(spanId, name, attributes);
}
recordError(spanId: string, error: Error): void {
console.error(` [ERROR] ${error.message}`, this.verbose ? error.stack : '');
this.memory.recordError(spanId, error);
}
async flush(): Promise<void> {
await this.memory.flush();
}
// Expose memory adapter methods for testing
getTrace(traceId: string) {
return this.memory.getTrace(traceId);
}
getAllTraces() {
return this.memory.getAllTraces();
}
clear() {
this.memory.clear();
}
}
export function createConsoleAdapter(options?: { verbose?: boolean }): ConsoleObservabilityAdapter {
return new ConsoleObservabilityAdapter(options);
}
@@ -0,0 +1,23 @@
/**
* Arize (Phoenix) Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class ArizeObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'arize';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'arize' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* Axiom Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class AxiomObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'axiom';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'axiom' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* Braintrust Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class BraintrustObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'braintrust';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'braintrust' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* Helicone Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class HeliconeObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'helicone';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'helicone' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* Laminar Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class LaminarObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'laminar';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'laminar' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,201 @@
/**
* Langfuse Observability Adapter
* Integrates with Langfuse for production observability
*/
import type {
ObservabilityAdapter,
TraceContext,
SpanContext,
SpanKind,
SpanStatus,
AttributionContext,
ProviderMetadata,
ObservabilityToolConfig
} from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
// Type stub for langfuse (optional dependency)
type LangfuseClient = {
trace: (opts: any) => any;
shutdownAsync: () => Promise<void>;
flushAsync: () => Promise<void>;
};
/**
* Langfuse Observability Adapter
* Wraps Langfuse SDK for trace/span management
*/
export class LangfuseObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'langfuse';
readonly isEnabled = true;
private client: any;
private memory: MemoryObservabilityAdapter;
private config: ObservabilityToolConfig;
private traces: Map<string, any> = new Map();
private spans: Map<string, any> = new Map();
constructor(config?: ObservabilityToolConfig) {
this.config = config || { name: 'langfuse' };
this.memory = new MemoryObservabilityAdapter();
}
async initialize(): Promise<void> {
try {
// Dynamic import for optional dependency
const langfuseModule = await import('langfuse' as string);
const Langfuse = langfuseModule.Langfuse || langfuseModule.default;
this.client = new Langfuse({
secretKey: this.config.apiKey || process.env.LANGFUSE_SECRET_KEY,
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
baseUrl: this.config.baseUrl || process.env.LANGFUSE_HOST,
});
} catch (error) {
console.warn('Langfuse SDK not available, falling back to memory adapter');
this.client = null;
}
}
async shutdown(): Promise<void> {
if (this.client) {
await this.client.shutdownAsync();
}
}
startTrace(
name: string,
metadata?: Record<string, unknown>,
attribution?: AttributionContext
): TraceContext {
const memoryCtx = this.memory.startTrace(name, metadata, attribution);
if (this.client) {
const trace = this.client.trace({
name,
metadata,
userId: attribution?.userId,
sessionId: attribution?.sessionId,
tags: attribution?.agentId ? [`agent:${attribution.agentId}`] : undefined,
});
this.traces.set(memoryCtx.traceId, trace);
}
return {
traceId: memoryCtx.traceId,
startSpan: (spanName: string, kind: SpanKind, parentId?: string) => {
return this.startSpan(memoryCtx.traceId, spanName, kind, parentId);
},
end: (status?: SpanStatus) => {
this.endTrace(memoryCtx.traceId, status);
}
};
}
endTrace(traceId: string, status?: SpanStatus): void {
this.memory.endTrace(traceId, status);
// Langfuse traces auto-complete
}
startSpan(
traceId: string,
name: string,
kind: SpanKind,
parentId?: string
): SpanContext {
const memoryCtx = this.memory.startSpan(traceId, name, kind, parentId);
if (this.client) {
const trace = this.traces.get(traceId);
if (trace) {
const parentSpan = parentId ? this.spans.get(parentId) : null;
const span = kind === 'llm'
? (parentSpan || trace).generation({ name })
: (parentSpan || trace).span({ name });
this.spans.set(memoryCtx.spanId, span);
}
}
const self = this;
return {
spanId: memoryCtx.spanId,
traceId,
addEvent(eventName: string, attributes?: Record<string, unknown>): void {
self.addEvent(memoryCtx.spanId, eventName, attributes);
},
setAttributes(attributes: Record<string, unknown>): void {
const span = self.spans.get(memoryCtx.spanId);
if (span) {
span.update({ metadata: attributes });
}
},
setProviderMetadata(metadata: ProviderMetadata): void {
const span = self.spans.get(memoryCtx.spanId);
if (span && span.update) {
span.update({
model: metadata.model,
usage: metadata.totalTokens ? {
promptTokens: metadata.promptTokens,
completionTokens: metadata.completionTokens,
totalTokens: metadata.totalTokens,
} : undefined,
metadata: {
provider: metadata.provider,
latencyMs: metadata.latencyMs,
retryCount: metadata.retryCount,
}
});
}
},
recordError(error: Error): void {
self.recordError(memoryCtx.spanId, error);
},
end(status?: SpanStatus): void {
self.endSpan(memoryCtx.spanId, status);
}
};
}
endSpan(
spanId: string,
status?: SpanStatus,
attributes?: Record<string, unknown>
): void {
this.memory.endSpan(spanId, status, attributes);
const span = this.spans.get(spanId);
if (span) {
span.end({
level: status === 'failed' ? 'ERROR' : undefined,
metadata: attributes,
});
}
}
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void {
this.memory.addEvent(spanId, name, attributes);
const span = this.spans.get(spanId);
if (span) {
span.event({ name, metadata: attributes });
}
}
recordError(spanId: string, error: Error): void {
this.memory.recordError(spanId, error);
const span = this.spans.get(spanId);
if (span) {
span.update({
level: 'ERROR',
statusMessage: error.message,
});
}
}
async flush(): Promise<void> {
if (this.client) {
await this.client.flushAsync();
}
}
}
@@ -0,0 +1,71 @@
/**
* LangSmith Observability Adapter
*/
import type {
ObservabilityAdapter,
TraceContext,
SpanContext,
SpanKind,
SpanStatus,
AttributionContext,
ProviderMetadata,
ObservabilityToolConfig
} from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class LangSmithObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'langsmith';
readonly isEnabled = true;
private client: any;
private memory: MemoryObservabilityAdapter;
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) {
this.config = config || { name: 'langsmith' };
this.memory = new MemoryObservabilityAdapter();
}
async initialize(): Promise<void> {
try {
// Dynamic import for optional dependency
const langsmithModule = await import('langsmith' as string);
const Client = langsmithModule.Client || langsmithModule.default;
this.client = new Client({
apiKey: this.config.apiKey || process.env.LANGCHAIN_API_KEY,
apiUrl: this.config.baseUrl,
});
} catch {
this.client = null;
}
}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext {
return this.memory.startTrace(name, metadata, attribution);
}
endTrace(traceId: string, status?: SpanStatus): void {
this.memory.endTrace(traceId, status);
}
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext {
return this.memory.startSpan(traceId, name, kind, parentId);
}
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void {
this.memory.endSpan(spanId, status, attributes);
}
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void {
this.memory.addEvent(spanId, name, attributes);
}
recordError(spanId: string, error: Error): void {
this.memory.recordError(spanId, error);
}
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* LangWatch Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class LangWatchObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'langwatch';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'langwatch' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* Maxim Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class MaximObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'maxim';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'maxim' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* Patronus Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class PatronusObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'patronus';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'patronus' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* Scorecard Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class ScorecardObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'scorecard';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'scorecard' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* SigNoz Observability Adapter (OpenTelemetry)
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class SigNozObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'signoz';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'signoz' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* Traceloop Observability Adapter (OpenLLMetry)
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class TraceloopObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'traceloop';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'traceloop' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,23 @@
/**
* Weave (Weights & Biases) Observability Adapter
*/
import type { ObservabilityAdapter, TraceContext, SpanContext, SpanKind, SpanStatus, AttributionContext, ObservabilityToolConfig } from '../../types';
import { MemoryObservabilityAdapter } from '../memory';
export class WeaveObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'weave';
readonly isEnabled = true;
private memory = new MemoryObservabilityAdapter();
private config: ObservabilityToolConfig;
constructor(config?: ObservabilityToolConfig) { this.config = config || { name: 'weave' }; }
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext { return this.memory.startTrace(name, metadata, attribution); }
endTrace(traceId: string, status?: SpanStatus): void { this.memory.endTrace(traceId, status); }
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext { return this.memory.startSpan(traceId, name, kind, parentId); }
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void { this.memory.endSpan(spanId, status, attributes); }
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void { this.memory.addEvent(spanId, name, attributes); }
recordError(spanId: string, error: Error): void { this.memory.recordError(spanId, error); }
async flush(): Promise<void> {}
}
@@ -0,0 +1,295 @@
/**
* Observability Adapters Index
* Exports all built-in adapters and lazy loaders for external adapters
*/
// Built-in adapters
export { NoopObservabilityAdapter, noopAdapter } from './noop';
export { MemoryObservabilityAdapter, createMemoryAdapter } from './memory';
export { ConsoleObservabilityAdapter, createConsoleAdapter } from './console';
// Re-export types
export type {
ObservabilityAdapter,
TraceContext,
SpanContext,
SpanKind,
SpanStatus,
SpanData,
TraceData,
SpanEvent,
AttributionContext,
ProviderMetadata,
ObservabilityToolConfig,
ObservabilityToolName,
ObservabilityToolInfo
} from '../types';
export {
OBSERVABILITY_TOOLS,
getObservabilityToolInfo,
listObservabilityTools,
hasObservabilityToolEnvVar
} from '../types';
import type { ObservabilityAdapter, ObservabilityToolConfig, ObservabilityToolName } from '../types';
import { noopAdapter } from './noop';
import { MemoryObservabilityAdapter } from './memory';
import { ConsoleObservabilityAdapter } from './console';
/**
* Adapter factory cache for lazy loading
*/
const adapterCache = new Map<string, ObservabilityAdapter>();
/**
* Create an observability adapter by name
* Uses lazy loading for external adapters to avoid bundling dependencies
*/
export async function createObservabilityAdapter(
name: ObservabilityToolName,
config?: ObservabilityToolConfig
): Promise<ObservabilityAdapter> {
// Check cache first
const cacheKey = `${name}:${JSON.stringify(config || {})}`;
const cached = adapterCache.get(cacheKey);
if (cached) return cached;
let adapter: ObservabilityAdapter;
switch (name) {
case 'noop':
adapter = noopAdapter;
break;
case 'memory':
adapter = new MemoryObservabilityAdapter();
break;
case 'console':
adapter = new ConsoleObservabilityAdapter({ verbose: config?.debug });
break;
case 'langfuse':
adapter = await createLangfuseAdapter(config);
break;
case 'langsmith':
adapter = await createLangSmithAdapter(config);
break;
case 'langwatch':
adapter = await createLangWatchAdapter(config);
break;
case 'arize':
adapter = await createArizeAdapter(config);
break;
case 'axiom':
adapter = await createAxiomAdapter(config);
break;
case 'braintrust':
adapter = await createBraintrustAdapter(config);
break;
case 'helicone':
adapter = await createHeliconeAdapter(config);
break;
case 'laminar':
adapter = await createLaminarAdapter(config);
break;
case 'maxim':
adapter = await createMaximAdapter(config);
break;
case 'patronus':
adapter = await createPatronusAdapter(config);
break;
case 'scorecard':
adapter = await createScorecardAdapter(config);
break;
case 'signoz':
adapter = await createSigNozAdapter(config);
break;
case 'traceloop':
adapter = await createTraceloopAdapter(config);
break;
case 'weave':
adapter = await createWeaveAdapter(config);
break;
default:
console.warn(`Unknown observability tool: ${name}, using noop adapter`);
adapter = noopAdapter;
}
// Initialize if needed
if (adapter.initialize) {
await adapter.initialize();
}
// Cache the adapter
adapterCache.set(cacheKey, adapter);
return adapter;
}
/**
* Clear adapter cache (for testing)
*/
export function clearAdapterCache(): void {
adapterCache.clear();
}
// ============================================================================
// Lazy-loaded external adapter factories
// These create wrapper adapters that delegate to external SDKs
// ============================================================================
async function createLangfuseAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { LangfuseObservabilityAdapter } = await import('./external/langfuse');
return new LangfuseObservabilityAdapter(config);
} catch (error) {
console.warn('Langfuse not available, using memory adapter. Install with: npm install langfuse');
return new MemoryObservabilityAdapter();
}
}
async function createLangSmithAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { LangSmithObservabilityAdapter } = await import('./external/langsmith');
return new LangSmithObservabilityAdapter(config);
} catch (error) {
console.warn('LangSmith not available, using memory adapter. Install with: npm install langsmith');
return new MemoryObservabilityAdapter();
}
}
async function createLangWatchAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { LangWatchObservabilityAdapter } = await import('./external/langwatch');
return new LangWatchObservabilityAdapter(config);
} catch (error) {
console.warn('LangWatch not available, using memory adapter. Install with: npm install langwatch');
return new MemoryObservabilityAdapter();
}
}
async function createArizeAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { ArizeObservabilityAdapter } = await import('./external/arize');
return new ArizeObservabilityAdapter(config);
} catch (error) {
console.warn('Arize not available, using memory adapter. Install with: npm install arize-phoenix');
return new MemoryObservabilityAdapter();
}
}
async function createAxiomAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { AxiomObservabilityAdapter } = await import('./external/axiom');
return new AxiomObservabilityAdapter(config);
} catch (error) {
console.warn('Axiom not available, using memory adapter. Install with: npm install @axiomhq/js');
return new MemoryObservabilityAdapter();
}
}
async function createBraintrustAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { BraintrustObservabilityAdapter } = await import('./external/braintrust');
return new BraintrustObservabilityAdapter(config);
} catch (error) {
console.warn('Braintrust not available, using memory adapter. Install with: npm install braintrust');
return new MemoryObservabilityAdapter();
}
}
async function createHeliconeAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { HeliconeObservabilityAdapter } = await import('./external/helicone');
return new HeliconeObservabilityAdapter(config);
} catch (error) {
console.warn('Helicone not available, using memory adapter. Install with: npm install @helicone/helicone');
return new MemoryObservabilityAdapter();
}
}
async function createLaminarAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { LaminarObservabilityAdapter } = await import('./external/laminar');
return new LaminarObservabilityAdapter(config);
} catch (error) {
console.warn('Laminar not available, using memory adapter. Install with: npm install @lmnr-ai/lmnr');
return new MemoryObservabilityAdapter();
}
}
async function createMaximAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { MaximObservabilityAdapter } = await import('./external/maxim');
return new MaximObservabilityAdapter(config);
} catch (error) {
console.warn('Maxim not available, using memory adapter. Install with: npm install @maximai/maxim-js');
return new MemoryObservabilityAdapter();
}
}
async function createPatronusAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { PatronusObservabilityAdapter } = await import('./external/patronus');
return new PatronusObservabilityAdapter(config);
} catch (error) {
console.warn('Patronus not available, using memory adapter. Install with: npm install patronus');
return new MemoryObservabilityAdapter();
}
}
async function createScorecardAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { ScorecardObservabilityAdapter } = await import('./external/scorecard');
return new ScorecardObservabilityAdapter(config);
} catch (error) {
console.warn('Scorecard not available, using memory adapter. Install with: npm install @scorecard-ai/sdk');
return new MemoryObservabilityAdapter();
}
}
async function createSigNozAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { SigNozObservabilityAdapter } = await import('./external/signoz');
return new SigNozObservabilityAdapter(config);
} catch (error) {
console.warn('SigNoz not available, using memory adapter. Install with: npm install @opentelemetry/api');
return new MemoryObservabilityAdapter();
}
}
async function createTraceloopAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { TraceloopObservabilityAdapter } = await import('./external/traceloop');
return new TraceloopObservabilityAdapter(config);
} catch (error) {
console.warn('Traceloop not available, using memory adapter. Install with: npm install @traceloop/node-server-sdk');
return new MemoryObservabilityAdapter();
}
}
async function createWeaveAdapter(config?: ObservabilityToolConfig): Promise<ObservabilityAdapter> {
try {
const { WeaveObservabilityAdapter } = await import('./external/weave');
return new WeaveObservabilityAdapter(config);
} catch (error) {
console.warn('Weave not available, using memory adapter. Install with: npm install weave');
return new MemoryObservabilityAdapter();
}
}
@@ -0,0 +1,250 @@
/**
* Memory Observability Adapter
* In-memory storage for development and testing
*/
import type {
ObservabilityAdapter,
TraceContext,
SpanContext,
SpanKind,
SpanStatus,
SpanData,
TraceData,
SpanEvent,
AttributionContext,
ProviderMetadata
} from '../types';
/**
* Generate a random ID
*/
function generateId(): string {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
/**
* Memory Span Context
*/
class MemorySpanContext implements SpanContext {
constructor(
public readonly spanId: string,
public readonly traceId: string,
private adapter: MemoryObservabilityAdapter
) {}
addEvent(name: string, attributes?: Record<string, unknown>): void {
this.adapter.addEvent(this.spanId, name, attributes);
}
setAttributes(attributes: Record<string, unknown>): void {
const span = this.adapter.getSpan(this.spanId);
if (span) {
span.attributes = { ...span.attributes, ...attributes };
}
}
setProviderMetadata(metadata: ProviderMetadata): void {
const span = this.adapter.getSpan(this.spanId);
if (span) {
span.providerMetadata = metadata;
}
}
recordError(error: Error): void {
this.adapter.recordError(this.spanId, error);
}
end(status: SpanStatus = 'completed'): void {
this.adapter.endSpan(this.spanId, status);
}
}
/**
* Memory Trace Context
*/
class MemoryTraceContext implements TraceContext {
constructor(
public readonly traceId: string,
private adapter: MemoryObservabilityAdapter
) {}
startSpan(name: string, kind: SpanKind, parentId?: string): SpanContext {
return this.adapter.startSpan(this.traceId, name, kind, parentId);
}
end(status: SpanStatus = 'completed'): void {
this.adapter.endTrace(this.traceId, status);
}
}
/**
* Memory Observability Adapter
* Stores all traces and spans in memory for development/testing
*/
export class MemoryObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'memory';
readonly isEnabled = true;
private traces: Map<string, TraceData> = new Map();
private spans: Map<string, SpanData> = new Map();
private maxTraces: number;
constructor(options: { maxTraces?: number } = {}) {
this.maxTraces = options.maxTraces || 1000;
}
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {
this.clear();
}
startTrace(
name: string,
metadata: Record<string, unknown> = {},
attribution?: AttributionContext
): TraceContext {
const traceId = generateId();
const trace: TraceData = {
id: traceId,
name,
startTime: Date.now(),
status: 'running',
spans: [],
metadata,
attribution
};
// Enforce max traces limit
if (this.traces.size >= this.maxTraces) {
const oldestKey = this.traces.keys().next().value;
if (oldestKey) {
this.traces.delete(oldestKey);
}
}
this.traces.set(traceId, trace);
return new MemoryTraceContext(traceId, this);
}
endTrace(traceId: string, status: SpanStatus = 'completed'): void {
const trace = this.traces.get(traceId);
if (trace) {
trace.endTime = Date.now();
trace.status = status;
}
}
startSpan(
traceId: string,
name: string,
kind: SpanKind,
parentId?: string
): SpanContext {
const spanId = generateId();
const span: SpanData = {
id: spanId,
traceId,
parentId,
name,
kind,
status: 'running',
startTime: Date.now(),
attributes: {},
events: []
};
this.spans.set(spanId, span);
const trace = this.traces.get(traceId);
if (trace) {
trace.spans.push(span);
}
return new MemorySpanContext(spanId, traceId, this);
}
endSpan(
spanId: string,
status: SpanStatus = 'completed',
attributes?: Record<string, unknown>
): void {
const span = this.spans.get(spanId);
if (span) {
span.endTime = Date.now();
span.status = status;
if (attributes) {
span.attributes = { ...span.attributes, ...attributes };
}
}
}
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void {
const span = this.spans.get(spanId);
if (span) {
const event: SpanEvent = {
name,
timestamp: Date.now(),
attributes
};
span.events.push(event);
}
}
recordError(spanId: string, error: Error): void {
const span = this.spans.get(spanId);
if (span) {
span.error = {
message: error.message,
stack: error.stack,
code: (error as any).code
};
span.status = 'failed';
}
}
async flush(): Promise<void> {
// No-op for memory adapter
}
// Utility methods for testing/debugging
getTrace(traceId: string): TraceData | undefined {
return this.traces.get(traceId);
}
getSpan(spanId: string): SpanData | undefined {
return this.spans.get(spanId);
}
getAllTraces(): TraceData[] {
return Array.from(this.traces.values());
}
getAllSpans(): SpanData[] {
return Array.from(this.spans.values());
}
getTraceSpans(traceId: string): SpanData[] {
return Array.from(this.spans.values()).filter(s => s.traceId === traceId);
}
clear(): void {
this.traces.clear();
this.spans.clear();
}
getStats(): { traces: number; spans: number } {
return {
traces: this.traces.size,
spans: this.spans.size
};
}
}
export function createMemoryAdapter(options?: { maxTraces?: number }): MemoryObservabilityAdapter {
return new MemoryObservabilityAdapter(options);
}
@@ -0,0 +1,97 @@
/**
* No-op Observability Adapter
* Default adapter that does nothing - zero overhead when observability is disabled
*/
import type {
ObservabilityAdapter,
TraceContext,
SpanContext,
SpanKind,
SpanStatus,
AttributionContext,
ProviderMetadata
} from '../types';
/**
* No-op span context - all operations are no-ops
*/
class NoopSpanContext implements SpanContext {
readonly spanId: string = '';
readonly traceId: string = '';
addEvent(_name: string, _attributes?: Record<string, unknown>): void {}
setAttributes(_attributes: Record<string, unknown>): void {}
setProviderMetadata(_metadata: ProviderMetadata): void {}
recordError(_error: Error): void {}
end(_status?: SpanStatus): void {}
}
/**
* No-op trace context - all operations are no-ops
*/
class NoopTraceContext implements TraceContext {
readonly traceId: string = '';
startSpan(_name: string, _kind: SpanKind, _parentId?: string): SpanContext {
return new NoopSpanContext();
}
end(_status?: SpanStatus): void {}
}
// Singleton instances
const noopSpanContext = new NoopSpanContext();
const noopTraceContext = new NoopTraceContext();
/**
* No-op Observability Adapter
*
* This adapter does nothing and has zero overhead.
* Used as the default when no observability is configured.
*/
export class NoopObservabilityAdapter implements ObservabilityAdapter {
readonly name = 'noop';
readonly isEnabled = false;
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
startTrace(
_name: string,
_metadata?: Record<string, unknown>,
_attribution?: AttributionContext
): TraceContext {
return noopTraceContext;
}
endTrace(_traceId: string, _status?: SpanStatus): void {}
startSpan(
_traceId: string,
_name: string,
_kind: SpanKind,
_parentId?: string
): SpanContext {
return noopSpanContext;
}
endSpan(
_spanId: string,
_status?: SpanStatus,
_attributes?: Record<string, unknown>
): void {}
addEvent(
_spanId: string,
_name: string,
_attributes?: Record<string, unknown>
): void {}
recordError(_spanId: string, _error: Error): void {}
async flush(): Promise<void> {}
}
// Export singleton instance
export const noopAdapter = new NoopObservabilityAdapter();
+85 -219
View File
@@ -1,238 +1,104 @@
/**
* Observability - Tracing, logging, and metrics hooks
* Observability Module - Unified tracing, logging, and metrics
*
* Supports 14+ observability integrations:
* - Langfuse, LangSmith, LangWatch
* - Arize AX, Axiom, Braintrust
* - Helicone, Laminar, Maxim
* - Patronus, Scorecard, SigNoz
* - Traceloop, Weave
*
* @example Basic usage
* ```typescript
* import { createObservabilityAdapter, setObservabilityAdapter } from 'praisonai';
*
* // Enable observability
* const adapter = await createObservabilityAdapter('langfuse');
* setObservabilityAdapter(adapter);
*
* // Use with agents
* const agent = new Agent({
* instructions: "You are helpful"
* });
* ```
*/
export type SpanKind = 'llm' | 'tool' | 'agent' | 'workflow' | 'custom';
export type SpanStatus = 'pending' | 'running' | 'completed' | 'failed';
// Re-export types from types.ts
export type {
SpanKind,
SpanStatus,
SpanData,
SpanEvent,
TraceData,
TraceContext,
SpanContext,
ObservabilityAdapter,
AttributionContext,
ProviderMetadata,
ObservabilityToolConfig,
ObservabilityToolName,
ObservabilityToolInfo
} from './types';
export interface SpanData {
id: string;
traceId: string;
parentId?: string;
name: string;
kind: SpanKind;
status: SpanStatus;
startTime: number;
endTime?: number;
attributes: Record<string, any>;
events: SpanEvent[];
}
export {
OBSERVABILITY_TOOLS,
getObservabilityToolInfo,
listObservabilityTools,
hasObservabilityToolEnvVar
} from './types';
export interface SpanEvent {
name: string;
timestamp: number;
attributes?: Record<string, any>;
}
// Re-export adapters
export {
NoopObservabilityAdapter,
noopAdapter,
MemoryObservabilityAdapter,
createMemoryAdapter,
ConsoleObservabilityAdapter,
createConsoleAdapter,
createObservabilityAdapter,
clearAdapterCache
} from './adapters';
export interface TraceData {
id: string;
name: string;
startTime: number;
endTime?: number;
status: SpanStatus;
spans: SpanData[];
metadata: Record<string, any>;
}
/**
* Observability Adapter Protocol
*/
export interface ObservabilityAdapter {
startTrace(name: string, metadata?: Record<string, any>): TraceContext;
endTrace(traceId: string, status?: SpanStatus): void;
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext;
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, any>): void;
addEvent(spanId: string, name: string, attributes?: Record<string, any>): void;
flush(): Promise<void>;
}
export interface TraceContext {
traceId: string;
startSpan(name: string, kind: SpanKind): SpanContext;
end(status?: SpanStatus): void;
}
export interface SpanContext {
spanId: string;
traceId: string;
addEvent(name: string, attributes?: Record<string, any>): void;
setAttributes(attributes: Record<string, any>): void;
end(status?: SpanStatus): void;
}
/**
* In-memory observability adapter for development
*/
export class MemoryObservabilityAdapter implements ObservabilityAdapter {
private traces: Map<string, TraceData> = new Map();
private spans: Map<string, SpanData> = new Map();
startTrace(name: string, metadata: Record<string, any> = {}): TraceContext {
const traceId = this.generateId();
const trace: TraceData = {
id: traceId,
name,
startTime: Date.now(),
status: 'running',
spans: [],
metadata
};
this.traces.set(traceId, trace);
const self = this;
return {
traceId,
startSpan(spanName: string, kind: SpanKind): SpanContext {
return self.startSpan(traceId, spanName, kind);
},
end(status: SpanStatus = 'completed'): void {
self.endTrace(traceId, status);
}
};
}
endTrace(traceId: string, status: SpanStatus = 'completed'): void {
const trace = this.traces.get(traceId);
if (trace) {
trace.endTime = Date.now();
trace.status = status;
}
}
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext {
const spanId = this.generateId();
const span: SpanData = {
id: spanId,
traceId,
parentId,
name,
kind,
status: 'running',
startTime: Date.now(),
attributes: {},
events: []
};
this.spans.set(spanId, span);
const trace = this.traces.get(traceId);
if (trace) {
trace.spans.push(span);
}
const self = this;
return {
spanId,
traceId,
addEvent(eventName: string, attributes?: Record<string, any>): void {
self.addEvent(spanId, eventName, attributes);
},
setAttributes(attributes: Record<string, any>): void {
const s = self.spans.get(spanId);
if (s) {
s.attributes = { ...s.attributes, ...attributes };
}
},
end(status: SpanStatus = 'completed'): void {
self.endSpan(spanId, status);
}
};
}
endSpan(spanId: string, status: SpanStatus = 'completed', attributes?: Record<string, any>): void {
const span = this.spans.get(spanId);
if (span) {
span.endTime = Date.now();
span.status = status;
if (attributes) {
span.attributes = { ...span.attributes, ...attributes };
}
}
}
addEvent(spanId: string, name: string, attributes?: Record<string, any>): void {
const span = this.spans.get(spanId);
if (span) {
span.events.push({
name,
timestamp: Date.now(),
attributes
});
}
}
async flush(): Promise<void> {
// No-op for memory adapter
}
// Utility methods
getTrace(traceId: string): TraceData | undefined {
return this.traces.get(traceId);
}
getSpan(spanId: string): SpanData | undefined {
return this.spans.get(spanId);
}
getAllTraces(): TraceData[] {
return Array.from(this.traces.values());
}
clear(): void {
this.traces.clear();
this.spans.clear();
}
private generateId(): string {
return Math.random().toString(36).substring(2, 15);
}
}
/**
* Console observability adapter for debugging
*/
export class ConsoleObservabilityAdapter implements ObservabilityAdapter {
private memory = new MemoryObservabilityAdapter();
startTrace(name: string, metadata?: Record<string, any>): TraceContext {
console.log(`[TRACE START] ${name}`, metadata || '');
return this.memory.startTrace(name, metadata);
}
endTrace(traceId: string, status?: SpanStatus): void {
console.log(`[TRACE END] ${traceId} - ${status || 'completed'}`);
this.memory.endTrace(traceId, status);
}
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext {
console.log(` [SPAN START] ${name} (${kind})`);
return this.memory.startSpan(traceId, name, kind, parentId);
}
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, any>): void {
console.log(` [SPAN END] ${spanId} - ${status || 'completed'}`, attributes || '');
this.memory.endSpan(spanId, status, attributes);
}
addEvent(spanId: string, name: string, attributes?: Record<string, any>): void {
console.log(` [EVENT] ${name}`, attributes || '');
this.memory.addEvent(spanId, name, attributes);
}
async flush(): Promise<void> {
await this.memory.flush();
}
}
// Import types for global adapter
import type { ObservabilityAdapter } from './types';
import { MemoryObservabilityAdapter as MemoryAdapter } from './adapters';
// Global observability instance
let globalAdapter: ObservabilityAdapter | null = null;
/**
* Set the global observability adapter
*/
export function setObservabilityAdapter(adapter: ObservabilityAdapter): void {
globalAdapter = adapter;
}
/**
* Get the global observability adapter (creates memory adapter if not set)
*/
export function getObservabilityAdapter(): ObservabilityAdapter {
if (!globalAdapter) {
globalAdapter = new MemoryObservabilityAdapter();
globalAdapter = new MemoryAdapter();
}
return globalAdapter;
}
/**
* Reset the global observability adapter (for testing)
*/
export function resetObservabilityAdapter(): void {
globalAdapter = null;
}
/**
* Create a trace helper function for easy integration
*/
export function trace(toolName: string = 'memory') {
return {
tool: toolName,
async getAdapter() {
const { createObservabilityAdapter } = await import('./adapters');
return createObservabilityAdapter(toolName as any);
}
};
}
Whitespace-only changes.
+345
View File
@@ -0,0 +1,345 @@
/**
* Observability Types - Unified tracing interface for all observability integrations
*/
export type SpanKind = 'llm' | 'tool' | 'agent' | 'workflow' | 'embedding' | 'retrieval' | 'custom';
export type SpanStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
/**
* Attribution context for multi-agent safety
*/
export interface AttributionContext {
agentId?: string;
runId?: string;
traceId?: string;
sessionId?: string;
parentSpanId?: string;
userId?: string;
teamId?: string;
}
/**
* Provider call metadata
*/
export interface ProviderMetadata {
provider: string;
model: string;
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
latencyMs?: number;
retryCount?: number;
region?: string;
cost?: number;
finishReason?: string;
}
/**
* Span event
*/
export interface SpanEvent {
name: string;
timestamp: number;
attributes?: Record<string, unknown>;
}
/**
* Span data
*/
export interface SpanData {
id: string;
traceId: string;
parentId?: string;
name: string;
kind: SpanKind;
status: SpanStatus;
startTime: number;
endTime?: number;
attributes: Record<string, unknown>;
events: SpanEvent[];
attribution?: AttributionContext;
providerMetadata?: ProviderMetadata;
error?: {
message: string;
stack?: string;
code?: string;
};
}
/**
* Trace data
*/
export interface TraceData {
id: string;
name: string;
startTime: number;
endTime?: number;
status: SpanStatus;
spans: SpanData[];
metadata: Record<string, unknown>;
attribution?: AttributionContext;
}
/**
* Span context for active spans
*/
export interface SpanContext {
spanId: string;
traceId: string;
addEvent(name: string, attributes?: Record<string, unknown>): void;
setAttributes(attributes: Record<string, unknown>): void;
setProviderMetadata(metadata: ProviderMetadata): void;
recordError(error: Error): void;
end(status?: SpanStatus): void;
}
/**
* Trace context for active traces
*/
export interface TraceContext {
traceId: string;
startSpan(name: string, kind: SpanKind, parentId?: string): SpanContext;
end(status?: SpanStatus): void;
}
/**
* Unified Observability Adapter Protocol
* All observability integrations must implement this interface
*/
export interface ObservabilityAdapter {
readonly name: string;
readonly isEnabled: boolean;
// Lifecycle
initialize?(): Promise<void>;
shutdown?(): Promise<void>;
// Tracing
startTrace(name: string, metadata?: Record<string, unknown>, attribution?: AttributionContext): TraceContext;
endTrace(traceId: string, status?: SpanStatus): void;
startSpan(traceId: string, name: string, kind: SpanKind, parentId?: string): SpanContext;
endSpan(spanId: string, status?: SpanStatus, attributes?: Record<string, unknown>): void;
// Events
addEvent(spanId: string, name: string, attributes?: Record<string, unknown>): void;
recordError(spanId: string, error: Error): void;
// Flush
flush(): Promise<void>;
}
/**
* Observability tool configuration
*/
export interface ObservabilityToolConfig {
name: string;
apiKey?: string;
baseUrl?: string;
projectId?: string;
environment?: string;
enabled?: boolean;
batchSize?: number;
flushInterval?: number;
debug?: boolean;
headers?: Record<string, string>;
}
/**
* Supported observability tools
*/
export type ObservabilityToolName =
| 'langfuse'
| 'langsmith'
| 'langwatch'
| 'arize'
| 'axiom'
| 'braintrust'
| 'helicone'
| 'laminar'
| 'maxim'
| 'patronus'
| 'scorecard'
| 'signoz'
| 'traceloop'
| 'weave'
| 'console'
| 'memory'
| 'noop';
/**
* Observability tool info
*/
export interface ObservabilityToolInfo {
name: ObservabilityToolName;
package?: string;
envKey: string;
description: string;
features: {
traces: boolean;
spans: boolean;
events: boolean;
errors: boolean;
metrics: boolean;
export: boolean;
};
docsUrl?: string;
}
/**
* All supported observability tools with their configurations
*/
export const OBSERVABILITY_TOOLS: Record<ObservabilityToolName, ObservabilityToolInfo> = {
langfuse: {
name: 'langfuse',
package: 'langfuse',
envKey: 'LANGFUSE_SECRET_KEY',
description: 'Langfuse observability platform',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://langfuse.com/docs'
},
langsmith: {
name: 'langsmith',
package: 'langsmith',
envKey: 'LANGCHAIN_API_KEY',
description: 'LangSmith by LangChain',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://docs.smith.langchain.com'
},
langwatch: {
name: 'langwatch',
package: 'langwatch',
envKey: 'LANGWATCH_API_KEY',
description: 'LangWatch monitoring',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://docs.langwatch.ai'
},
arize: {
name: 'arize',
package: 'arize-phoenix',
envKey: 'ARIZE_API_KEY',
description: 'Arize AX (Phoenix)',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://docs.arize.com'
},
axiom: {
name: 'axiom',
package: '@axiomhq/js',
envKey: 'AXIOM_TOKEN',
description: 'Axiom logging and analytics',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://axiom.co/docs'
},
braintrust: {
name: 'braintrust',
package: 'braintrust',
envKey: 'BRAINTRUST_API_KEY',
description: 'Braintrust AI evaluation',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://www.braintrust.dev/docs'
},
helicone: {
name: 'helicone',
package: '@helicone/helicone',
envKey: 'HELICONE_API_KEY',
description: 'Helicone observability proxy',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://docs.helicone.ai'
},
laminar: {
name: 'laminar',
package: '@lmnr-ai/lmnr',
envKey: 'LMNR_PROJECT_API_KEY',
description: 'Laminar AI observability',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://docs.lmnr.ai'
},
maxim: {
name: 'maxim',
package: '@maximai/maxim-js',
envKey: 'MAXIM_API_KEY',
description: 'Maxim AI testing',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://docs.getmaxim.ai'
},
patronus: {
name: 'patronus',
package: 'patronus',
envKey: 'PATRONUS_API_KEY',
description: 'Patronus AI evaluation',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://docs.patronus.ai'
},
scorecard: {
name: 'scorecard',
package: '@scorecard-ai/sdk',
envKey: 'SCORECARD_API_KEY',
description: 'Scorecard AI testing',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://docs.getscorecard.ai'
},
signoz: {
name: 'signoz',
package: '@opentelemetry/api',
envKey: 'SIGNOZ_ACCESS_TOKEN',
description: 'SigNoz OpenTelemetry',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://signoz.io/docs'
},
traceloop: {
name: 'traceloop',
package: '@traceloop/node-server-sdk',
envKey: 'TRACELOOP_API_KEY',
description: 'Traceloop OpenLLMetry',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://traceloop.com/docs'
},
weave: {
name: 'weave',
package: 'weave',
envKey: 'WANDB_API_KEY',
description: 'Weights & Biases Weave',
features: { traces: true, spans: true, events: true, errors: true, metrics: true, export: true },
docsUrl: 'https://wandb.ai/site/weave'
},
console: {
name: 'console',
envKey: '',
description: 'Console logging (built-in)',
features: { traces: true, spans: true, events: true, errors: true, metrics: false, export: false }
},
memory: {
name: 'memory',
envKey: '',
description: 'In-memory storage (built-in)',
features: { traces: true, spans: true, events: true, errors: true, metrics: false, export: false }
},
noop: {
name: 'noop',
envKey: '',
description: 'No-op adapter (disabled)',
features: { traces: false, spans: false, events: false, errors: false, metrics: false, export: false }
}
};
/**
* Get observability tool info
*/
export function getObservabilityToolInfo(name: ObservabilityToolName): ObservabilityToolInfo | undefined {
return OBSERVABILITY_TOOLS[name];
}
/**
* List all observability tools
*/
export function listObservabilityTools(): ObservabilityToolInfo[] {
return Object.values(OBSERVABILITY_TOOLS);
}
/**
* Check if observability tool has required env var
*/
export function hasObservabilityToolEnvVar(name: ObservabilityToolName): boolean {
const info = OBSERVABILITY_TOOLS[name];
if (!info || !info.envKey) return true; // Built-in tools don't need env vars
return !!process.env[info.envKey];
}
@@ -0,0 +1,122 @@
/**
* Airweave Tool
*
* Context retrieval and semantic search across connected data sources.
* Package: @airweave/sdk
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError, MissingEnvVarError } from '../registry/types';
export const AIRWEAVE_METADATA: ToolMetadata = {
id: 'airweave',
displayName: 'Airweave',
description: 'Context retrieval and semantic search across connected data sources for RAG applications',
tags: ['rag', 'semantic-search', 'retrieval', 'context'],
requiredEnv: ['AIRWEAVE_API_KEY'],
optionalEnv: ['AIRWEAVE_BASE_URL'],
install: {
npm: 'npm install @airweave/sdk',
pnpm: 'pnpm add @airweave/sdk',
yarn: 'yarn add @airweave/sdk',
bun: 'bun add @airweave/sdk',
},
docsSlug: 'tools/airweave',
capabilities: {
rag: true,
search: true,
},
packageName: '@airweave/sdk',
};
export interface AirweaveSearchConfig {
limit?: number;
threshold?: number;
sources?: string[];
}
export interface AirweaveSearchInput {
query: string;
}
export interface AirweaveSearchResult {
results: Array<{
content: string;
source: string;
score: number;
metadata?: Record<string, unknown>;
}>;
}
async function loadAirweavePackage() {
if (!process.env.AIRWEAVE_API_KEY) {
throw new MissingEnvVarError(
AIRWEAVE_METADATA.id,
'AIRWEAVE_API_KEY',
AIRWEAVE_METADATA.docsSlug
);
}
try {
return await import('@airweave/sdk');
} catch {
throw new MissingDependencyError(
AIRWEAVE_METADATA.id,
AIRWEAVE_METADATA.packageName,
AIRWEAVE_METADATA.install,
AIRWEAVE_METADATA.requiredEnv,
AIRWEAVE_METADATA.docsSlug
);
}
}
/**
* Create an Airweave Search tool
*/
export function airweaveSearch(config?: AirweaveSearchConfig): PraisonTool<AirweaveSearchInput, AirweaveSearchResult> {
return {
name: 'airweaveSearch',
description: 'Search across connected data sources using semantic search. Returns relevant context for RAG applications.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query',
},
},
required: ['query'],
},
execute: async (input: AirweaveSearchInput, context?: ToolExecutionContext): Promise<AirweaveSearchResult> => {
const pkg = await loadAirweavePackage();
// Airweave SDK uses a client-based API
const AirweaveClient = (pkg as Record<string, unknown>).AirweaveClient ||
(pkg as Record<string, unknown>).Airweave ||
(pkg as Record<string, unknown>).default;
if (AirweaveClient && typeof AirweaveClient === 'function') {
const client = new (AirweaveClient as new (options: { apiKey: string; baseUrl?: string }) => {
search: (query: string, options?: AirweaveSearchConfig) => Promise<{ results: Array<{ content: string; source: string; score: number; metadata?: Record<string, unknown> }> }>;
})({
apiKey: process.env.AIRWEAVE_API_KEY!,
baseUrl: process.env.AIRWEAVE_BASE_URL,
});
const result = await client.search(input.query, config);
return {
results: result.results || [],
};
}
return { results: [] };
},
};
}
/**
* Factory function for registry
*/
export function createAirweaveSearchTool(config?: AirweaveSearchConfig): PraisonTool<unknown, unknown> {
return airweaveSearch(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,267 @@
/**
* Amazon Bedrock AgentCore Tools
*
* Code Interpreter and Browser automation powered by AWS.
* Package: bedrock-agentcore
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError } from '../registry/types';
export const BEDROCK_AGENTCORE_METADATA: ToolMetadata = {
id: 'bedrock-agentcore',
displayName: 'Amazon Bedrock AgentCore',
description: 'Code interpreter and browser automation powered by AWS Bedrock AgentCore',
tags: ['sandbox', 'code', 'browser', 'aws'],
requiredEnv: [], // Uses AWS credentials from environment/config
optionalEnv: ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION'],
install: {
npm: 'npm install bedrock-agentcore',
pnpm: 'pnpm add bedrock-agentcore',
yarn: 'yarn add bedrock-agentcore',
bun: 'bun add bedrock-agentcore',
},
docsSlug: 'tools/bedrock-agentcore',
capabilities: {
sandbox: true,
code: true,
browser: true,
},
packageName: 'bedrock-agentcore',
};
// Code Interpreter types
export interface CodeInterpreterConfig {
language?: 'python' | 'javascript' | 'typescript';
timeout?: number;
}
export interface CodeInterpreterInput {
code: string;
}
export interface CodeInterpreterResult {
output: string;
success: boolean;
error?: string;
}
// Browser types
export interface BrowserConfig {
headless?: boolean;
timeout?: number;
}
export interface BrowserNavigateInput {
url: string;
}
export interface BrowserClickInput {
selector: string;
}
export interface BrowserFillInput {
selector: string;
value: string;
}
export interface BrowserResult {
success: boolean;
data?: unknown;
error?: string;
}
async function loadBedrockPackage() {
try {
// @ts-ignore - Optional dependency
return await import('bedrock-agentcore');
} catch {
throw new MissingDependencyError(
BEDROCK_AGENTCORE_METADATA.id,
BEDROCK_AGENTCORE_METADATA.packageName,
BEDROCK_AGENTCORE_METADATA.install,
BEDROCK_AGENTCORE_METADATA.requiredEnv,
BEDROCK_AGENTCORE_METADATA.docsSlug
);
}
}
/**
* Create a Bedrock Code Interpreter tool
*/
export function bedrockCodeInterpreter(config?: CodeInterpreterConfig): PraisonTool<CodeInterpreterInput, CodeInterpreterResult> {
return {
name: 'bedrockCodeInterpreter',
description: 'Execute code in a secure AWS-managed sandbox. Supports Python, JavaScript, and TypeScript.',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'The code to execute',
},
},
required: ['code'],
},
execute: async (input: CodeInterpreterInput, context?: ToolExecutionContext): Promise<CodeInterpreterResult> => {
const pkg = await loadBedrockPackage();
// Try to get CodeInterpreterTools from the package
const CodeInterpreterTools = (pkg as Record<string, unknown>).CodeInterpreterTools;
if (CodeInterpreterTools && typeof CodeInterpreterTools === 'function') {
const interpreter = new (CodeInterpreterTools as new () => {
tools: Record<string, { execute: (input: unknown) => Promise<unknown> }>;
})();
// Find the execute code tool
const executeTool = interpreter.tools?.executeCode;
if (executeTool && typeof executeTool.execute === 'function') {
const result = await executeTool.execute(input);
return {
output: String(result),
success: true,
};
}
}
return { output: '', success: false, error: 'Code interpreter not available' };
},
};
}
/**
* Create a Bedrock Browser Navigate tool
*/
export function bedrockBrowserNavigate(config?: BrowserConfig): PraisonTool<BrowserNavigateInput, BrowserResult> {
return {
name: 'bedrockBrowserNavigate',
description: 'Navigate to a URL using cloud-based browser automation.',
parameters: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The URL to navigate to',
},
},
required: ['url'],
},
execute: async (input: BrowserNavigateInput, context?: ToolExecutionContext): Promise<BrowserResult> => {
const pkg = await loadBedrockPackage();
const BrowserTools = (pkg as Record<string, unknown>).BrowserTools;
if (BrowserTools && typeof BrowserTools === 'function') {
const browser = new (BrowserTools as new () => {
tools: Record<string, { execute: (input: unknown) => Promise<unknown> }>;
})();
const navigateTool = browser.tools?.navigate;
if (navigateTool && typeof navigateTool.execute === 'function') {
await navigateTool.execute(input);
return { success: true };
}
}
return { success: false, error: 'Browser tools not available' };
},
};
}
/**
* Create a Bedrock Browser Click tool
*/
export function bedrockBrowserClick(config?: BrowserConfig): PraisonTool<BrowserClickInput, BrowserResult> {
return {
name: 'bedrockBrowserClick',
description: 'Click an element on the page using a CSS selector.',
parameters: {
type: 'object',
properties: {
selector: {
type: 'string',
description: 'CSS selector for the element to click',
},
},
required: ['selector'],
},
execute: async (input: BrowserClickInput, context?: ToolExecutionContext): Promise<BrowserResult> => {
const pkg = await loadBedrockPackage();
const BrowserTools = (pkg as Record<string, unknown>).BrowserTools;
if (BrowserTools && typeof BrowserTools === 'function') {
const browser = new (BrowserTools as new () => {
tools: Record<string, { execute: (input: unknown) => Promise<unknown> }>;
})();
const clickTool = browser.tools?.click;
if (clickTool && typeof clickTool.execute === 'function') {
await clickTool.execute(input);
return { success: true };
}
}
return { success: false, error: 'Browser tools not available' };
},
};
}
/**
* Create a Bedrock Browser Fill tool
*/
export function bedrockBrowserFill(config?: BrowserConfig): PraisonTool<BrowserFillInput, BrowserResult> {
return {
name: 'bedrockBrowserFill',
description: 'Fill a form field with text using a CSS selector.',
parameters: {
type: 'object',
properties: {
selector: {
type: 'string',
description: 'CSS selector for the input element',
},
value: {
type: 'string',
description: 'The value to fill in',
},
},
required: ['selector', 'value'],
},
execute: async (input: BrowserFillInput, context?: ToolExecutionContext): Promise<BrowserResult> => {
const pkg = await loadBedrockPackage();
const BrowserTools = (pkg as Record<string, unknown>).BrowserTools;
if (BrowserTools && typeof BrowserTools === 'function') {
const browser = new (BrowserTools as new () => {
tools: Record<string, { execute: (input: unknown) => Promise<unknown> }>;
})();
const fillTool = browser.tools?.fill;
if (fillTool && typeof fillTool.execute === 'function') {
await fillTool.execute(input);
return { success: true };
}
}
return { success: false, error: 'Browser tools not available' };
},
};
}
/**
* Factory functions for registry
*/
export function createBedrockCodeInterpreterTool(config?: CodeInterpreterConfig): PraisonTool<unknown, unknown> {
return bedrockCodeInterpreter(config) as PraisonTool<unknown, unknown>;
}
export function createBedrockBrowserNavigateTool(config?: BrowserConfig): PraisonTool<unknown, unknown> {
return bedrockBrowserNavigate(config) as PraisonTool<unknown, unknown>;
}
export function createBedrockBrowserClickTool(config?: BrowserConfig): PraisonTool<unknown, unknown> {
return bedrockBrowserClick(config) as PraisonTool<unknown, unknown>;
}
export function createBedrockBrowserFillTool(config?: BrowserConfig): PraisonTool<unknown, unknown> {
return bedrockBrowserFill(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,135 @@
/**
* Code Execution Tool (Vercel Sandbox)
*
* Execute Python code in a sandboxed Vercel environment.
* Package: ai-sdk-tool-code-execution
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError, MissingEnvVarError } from '../registry/types';
export const CODE_EXECUTION_METADATA: ToolMetadata = {
id: 'code-execution',
displayName: 'Code Execution (Vercel Sandbox)',
description: 'Execute Python code in a secure Vercel Sandbox environment',
tags: ['sandbox', 'code', 'python', 'execution'],
requiredEnv: [], // VERCEL_OIDC_TOKEN is auto-provided in Vercel, or use alternative auth
optionalEnv: ['VERCEL_OIDC_TOKEN', 'VERCEL_TOKEN', 'VERCEL_TEAM_ID', 'VERCEL_PROJECT_ID'],
install: {
npm: 'npm install ai-sdk-tool-code-execution',
pnpm: 'pnpm add ai-sdk-tool-code-execution',
yarn: 'yarn add ai-sdk-tool-code-execution',
bun: 'bun add ai-sdk-tool-code-execution',
},
docsSlug: 'tools/code-execution',
capabilities: {
sandbox: true,
code: true,
},
packageName: 'ai-sdk-tool-code-execution',
};
export interface CodeExecutionConfig {
/** Enable debug logging */
debug?: boolean;
/** Execution timeout in ms */
timeoutMs?: number;
}
export interface CodeExecutionInput {
/** Python code to execute */
code: string;
}
export interface CodeExecutionOutput {
/** Execution result/output */
result: string;
/** Standard output */
stdout?: string;
/** Standard error */
stderr?: string;
/** Execution success */
success: boolean;
}
/**
* Create a Code Execution tool instance
*/
export function codeExecution(config?: CodeExecutionConfig): PraisonTool<CodeExecutionInput, CodeExecutionOutput> {
// Lazy load the package
let executeCodeFn: ((options?: { debug?: boolean }) => unknown) | null = null;
const loadPackage = async () => {
if (executeCodeFn) return executeCodeFn;
try {
// @ts-ignore - Optional dependency
const pkg = await import('ai-sdk-tool-code-execution');
executeCodeFn = pkg.executeCode;
return executeCodeFn;
} catch (error) {
throw new MissingDependencyError(
CODE_EXECUTION_METADATA.id,
CODE_EXECUTION_METADATA.packageName,
CODE_EXECUTION_METADATA.install,
CODE_EXECUTION_METADATA.requiredEnv,
CODE_EXECUTION_METADATA.docsSlug
);
}
};
return {
name: 'executeCode',
description: 'Execute Python code in a secure sandbox environment. Use this for calculations, data processing, and computational tasks.',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'Python code to execute',
},
},
required: ['code'],
},
execute: async (input: CodeExecutionInput, context?: ToolExecutionContext): Promise<CodeExecutionOutput> => {
const executeCode = await loadPackage();
// The AI SDK tool returns a tool definition, not a direct executor
// We need to use it with the AI SDK's generateText
// For direct execution, we'll create a wrapper
try {
// Create the tool with config
const tool = (executeCode as Function)({ debug: config?.debug });
// The tool is meant to be used with AI SDK's generateText
// For standalone use, we execute it directly if possible
if (tool && typeof tool.execute === 'function') {
const result = await tool.execute(input);
return {
result: String(result),
success: true,
};
}
// If the tool doesn't have a direct execute, return info about usage
return {
result: 'Code execution tool created. Use with AI SDK generateText for full functionality.',
success: true,
};
} catch (error) {
return {
result: '',
stderr: error instanceof Error ? error.message : String(error),
success: false,
};
}
},
};
}
/**
* Factory function for registry
*/
export function createCodeExecutionTool(config?: CodeExecutionConfig): PraisonTool<unknown, unknown> {
return codeExecution(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,226 @@
/**
* Code Mode Tool
*
* Transform MCP tools and AI SDK tools into code-mode execution.
* Allows writing code to a sandbox FS, importing tools, and running them.
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
export const CODE_MODE_METADATA: ToolMetadata = {
id: 'code-mode',
displayName: 'Code Mode',
description: 'Execute code that can import and use other tools in a sandboxed environment',
tags: ['sandbox', 'code', 'mcp', 'tools'],
requiredEnv: [],
optionalEnv: [],
install: {
npm: '# Built-in, no installation required',
pnpm: '# Built-in, no installation required',
yarn: '# Built-in, no installation required',
bun: '# Built-in, no installation required',
},
docsSlug: 'tools/code-mode',
capabilities: {
sandbox: true,
code: true,
},
packageName: 'praisonai', // Built-in
};
export interface CodeModeConfig {
/** Allowed tools that can be imported */
allowedTools?: string[];
/** Blocked tools that cannot be imported */
blockedTools?: string[];
/** Enable network access (default: false) */
allowNetwork?: boolean;
/** Allowed file paths (glob patterns) */
allowedPaths?: string[];
/** Execution timeout in ms (default: 30000) */
timeoutMs?: number;
/** Maximum memory in MB (default: 128) */
maxMemoryMb?: number;
}
export interface CodeModeInput {
/** Code to execute */
code: string;
/** Files to write to the sandbox before execution */
files?: Record<string, string>;
/** Environment variables for execution */
env?: Record<string, string>;
}
export interface CodeModeResult {
/** Execution output */
output: string;
/** Standard output */
stdout: string;
/** Standard error */
stderr: string;
/** Exit code */
exitCode: number;
/** Files created/modified during execution */
files?: Record<string, string>;
/** Execution success */
success: boolean;
/** Error message if failed */
error?: string;
}
/**
* Create a Code Mode tool
*/
export function codeMode(config?: CodeModeConfig): PraisonTool<CodeModeInput, CodeModeResult> {
const settings = {
allowNetwork: false,
timeoutMs: 30000,
maxMemoryMb: 128,
...config,
};
return {
name: 'codeMode',
description: 'Execute code in a sandboxed environment with access to imported tools. Write files, run code, and get results.',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'The code to execute',
},
files: {
type: 'object',
description: 'Files to write to the sandbox before execution (path -> content)',
},
env: {
type: 'object',
description: 'Environment variables for execution',
},
},
required: ['code'],
},
execute: async (input: CodeModeInput, context?: ToolExecutionContext): Promise<CodeModeResult> => {
const { code, files, env } = input;
// Validate code doesn't contain blocked patterns
const blockedPatterns = [
/require\s*\(\s*['"]child_process['"]\s*\)/,
/require\s*\(\s*['"]fs['"]\s*\)/,
/import\s+.*from\s+['"]child_process['"]/,
/process\.exit/,
/eval\s*\(/,
];
if (!settings.allowNetwork) {
blockedPatterns.push(
/require\s*\(\s*['"]http['"]\s*\)/,
/require\s*\(\s*['"]https['"]\s*\)/,
/require\s*\(\s*['"]net['"]\s*\)/,
/fetch\s*\(/,
);
}
for (const pattern of blockedPatterns) {
if (pattern.test(code)) {
return {
output: '',
stdout: '',
stderr: `Blocked pattern detected: ${pattern.source}`,
exitCode: 1,
success: false,
error: 'Code contains blocked patterns for security',
};
}
}
// Check for blocked tools
if (settings.blockedTools && settings.blockedTools.length > 0) {
for (const tool of settings.blockedTools) {
if (code.includes(tool)) {
return {
output: '',
stdout: '',
stderr: `Blocked tool: ${tool}`,
exitCode: 1,
success: false,
error: `Tool "${tool}" is not allowed`,
};
}
}
}
try {
// Create a sandboxed execution context
// In a real implementation, this would use a proper sandbox like vm2 or isolated-vm
// For now, we provide a safe execution wrapper
const sandbox = {
console: {
log: (...args: unknown[]) => stdout.push(args.map(String).join(' ')),
error: (...args: unknown[]) => stderr.push(args.map(String).join(' ')),
warn: (...args: unknown[]) => stderr.push(args.map(String).join(' ')),
},
setTimeout: undefined,
setInterval: undefined,
setImmediate: undefined,
process: undefined,
require: undefined,
__dirname: '/sandbox',
__filename: '/sandbox/index.js',
env: env || {},
files: files || {},
};
const stdout: string[] = [];
const stderr: string[] = [];
// Execute with timeout
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('Execution timeout')), settings.timeoutMs);
});
const executePromise = new Promise<string>((resolve, reject) => {
try {
// Create a function from the code
const fn = new Function(
'sandbox',
`with (sandbox) { ${code} }`
);
const result = fn(sandbox);
resolve(String(result ?? ''));
} catch (error) {
reject(error);
}
});
const result = await Promise.race([executePromise, timeoutPromise]);
return {
output: result,
stdout: stdout.join('\n'),
stderr: stderr.join('\n'),
exitCode: 0,
success: true,
};
} catch (error) {
return {
output: '',
stdout: '',
stderr: error instanceof Error ? error.message : String(error),
exitCode: 1,
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
},
};
}
/**
* Factory function for registry
*/
export function createCodeModeTool(config?: CodeModeConfig): PraisonTool<unknown, unknown> {
return codeMode(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,214 @@
/**
* Custom Tool Registration API
*
* Clean API to register custom tools (local + npm) with minimal friction.
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext, ToolParameterSchema } from '../registry/types';
import { getToolsRegistry } from '../registry/registry';
export interface CustomToolConfig<TInput = unknown, TOutput = unknown> {
/** Unique tool identifier */
id: string;
/** Display name */
name: string;
/** Tool description (used by LLM) */
description: string;
/** Parameter schema */
parameters?: ToolParameterSchema;
/** Execute function */
execute: (input: TInput, context?: ToolExecutionContext) => Promise<TOutput> | TOutput;
/** Tags for categorization */
tags?: string[];
/** Documentation slug */
docsSlug?: string;
}
/**
* Register a custom tool with the global registry
*/
export function registerCustomTool<TInput = unknown, TOutput = unknown>(
config: CustomToolConfig<TInput, TOutput>
): PraisonTool<TInput, TOutput> {
const metadata: ToolMetadata = {
id: config.id,
displayName: config.name,
description: config.description,
tags: config.tags || ['custom'],
requiredEnv: [],
optionalEnv: [],
install: {
npm: '# Custom tool - no installation required',
pnpm: '# Custom tool - no installation required',
yarn: '# Custom tool - no installation required',
bun: '# Custom tool - no installation required',
},
docsSlug: config.docsSlug || `tools/custom/${config.id}`,
capabilities: {},
packageName: 'custom',
};
const tool: PraisonTool<TInput, TOutput> = {
name: config.name,
description: config.description,
parameters: config.parameters || { type: 'object', properties: {} },
execute: async (input: TInput, context?: ToolExecutionContext): Promise<TOutput> => {
return config.execute(input, context);
},
};
// Register with the global registry
const registry = getToolsRegistry();
registry.register(metadata, () => tool as PraisonTool<unknown, unknown>);
return tool;
}
/**
* Create a custom tool without registering it
*/
export function createCustomTool<TInput = unknown, TOutput = unknown>(
config: CustomToolConfig<TInput, TOutput>
): PraisonTool<TInput, TOutput> {
return {
name: config.name,
description: config.description,
parameters: config.parameters || { type: 'object', properties: {} },
execute: async (input: TInput, context?: ToolExecutionContext): Promise<TOutput> => {
return config.execute(input, context);
},
};
}
/**
* Register a tool from an npm package
*/
export async function registerNpmTool(
packageName: string,
toolName?: string
): Promise<PraisonTool<unknown, unknown>> {
try {
const pkg = await import(packageName);
// Try to find the tool export
const toolExport = toolName
? pkg[toolName]
: pkg.default || pkg.tool || pkg[Object.keys(pkg)[0]];
if (!toolExport) {
throw new Error(`Could not find tool export in package "${packageName}"`);
}
// If it's a function, call it to get the tool
const tool = typeof toolExport === 'function' ? toolExport() : toolExport;
// Validate it has the required properties
if (!tool.name || !tool.execute) {
throw new Error(`Invalid tool structure in package "${packageName}"`);
}
// Register with the registry
const metadata: ToolMetadata = {
id: `npm:${packageName}${toolName ? `:${toolName}` : ''}`,
displayName: tool.name,
description: tool.description || `Tool from ${packageName}`,
tags: ['npm', 'external'],
requiredEnv: [],
optionalEnv: [],
install: {
npm: `npm install ${packageName}`,
pnpm: `pnpm add ${packageName}`,
yarn: `yarn add ${packageName}`,
bun: `bun add ${packageName}`,
},
docsSlug: `tools/npm/${packageName.replace(/[@/]/g, '-')}`,
capabilities: {},
packageName,
};
const registry = getToolsRegistry();
registry.register(metadata, () => tool);
return tool;
} catch (error) {
throw new Error(
`Failed to register tool from "${packageName}": ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Register a tool from a local file path
*/
export async function registerLocalTool(
filePath: string,
toolName?: string
): Promise<PraisonTool<unknown, unknown>> {
try {
// Resolve the path
const resolvedPath = require.resolve(filePath, { paths: [process.cwd()] });
const pkg = await import(resolvedPath);
const toolExport = toolName
? pkg[toolName]
: pkg.default || pkg.tool || pkg[Object.keys(pkg)[0]];
if (!toolExport) {
throw new Error(`Could not find tool export in file "${filePath}"`);
}
const tool = typeof toolExport === 'function' ? toolExport() : toolExport;
if (!tool.name || !tool.execute) {
throw new Error(`Invalid tool structure in file "${filePath}"`);
}
const metadata: ToolMetadata = {
id: `local:${filePath}${toolName ? `:${toolName}` : ''}`,
displayName: tool.name,
description: tool.description || `Tool from ${filePath}`,
tags: ['local', 'custom'],
requiredEnv: [],
optionalEnv: [],
install: {
npm: '# Local tool - no installation required',
pnpm: '# Local tool - no installation required',
yarn: '# Local tool - no installation required',
bun: '# Local tool - no installation required',
},
docsSlug: `tools/local`,
capabilities: {},
packageName: 'local',
};
const registry = getToolsRegistry();
registry.register(metadata, () => tool);
return tool;
} catch (error) {
throw new Error(
`Failed to register tool from "${filePath}": ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Decorator for creating tools from class methods
*/
export function Tool(config: Omit<CustomToolConfig, 'execute'>) {
return function (
target: unknown,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
// Create and register the tool
registerCustomTool({
...config,
execute: originalMethod,
});
return descriptor;
};
}
+127
View File
@@ -0,0 +1,127 @@
/**
* Exa Web Search Tool
*
* AI-powered web search with semantic understanding.
* Package: @exalabs/ai-sdk
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError, MissingEnvVarError } from '../registry/types';
export const EXA_METADATA: ToolMetadata = {
id: 'exa',
displayName: 'Exa Web Search',
description: 'AI-powered web search with semantic understanding and content extraction',
tags: ['search', 'web', 'semantic'],
requiredEnv: ['EXA_API_KEY'],
optionalEnv: [],
install: {
npm: 'npm install @exalabs/ai-sdk',
pnpm: 'pnpm add @exalabs/ai-sdk',
yarn: 'yarn add @exalabs/ai-sdk',
bun: 'bun add @exalabs/ai-sdk',
},
docsSlug: 'tools/exa',
capabilities: {
search: true,
extract: true,
},
packageName: '@exalabs/ai-sdk',
};
export interface ExaSearchConfig {
type?: 'auto' | 'neural' | 'fast' | 'deep';
category?: 'company' | 'research paper' | 'news' | 'pdf' | 'github' | 'personal site' | 'linkedin profile' | 'financial report';
numResults?: number;
includeDomains?: string[];
excludeDomains?: string[];
startPublishedDate?: string;
endPublishedDate?: string;
includeText?: string[];
excludeText?: string[];
userLocation?: string;
contents?: {
text?: { maxCharacters?: number; includeHtmlTags?: boolean };
summary?: { query?: string } | boolean;
livecrawl?: 'never' | 'fallback' | 'always' | 'preferred';
livecrawlTimeout?: number;
};
}
export interface ExaSearchInput {
query: string;
}
export interface ExaSearchResult {
results: Array<{
title: string;
url: string;
text?: string;
summary?: string;
publishedDate?: string;
author?: string;
score?: number;
}>;
}
async function loadExaPackage() {
if (!process.env.EXA_API_KEY) {
throw new MissingEnvVarError(
EXA_METADATA.id,
'EXA_API_KEY',
EXA_METADATA.docsSlug
);
}
try {
// @ts-ignore - optional dependency
return await import('@exalabs/ai-sdk');
} catch {
throw new MissingDependencyError(
EXA_METADATA.id,
EXA_METADATA.packageName,
EXA_METADATA.install,
EXA_METADATA.requiredEnv,
EXA_METADATA.docsSlug
);
}
}
/**
* Create an Exa Web Search tool
*/
export function exaSearch(config?: ExaSearchConfig): PraisonTool<ExaSearchInput, ExaSearchResult> {
return {
name: 'webSearch',
description: 'Search the web using Exa AI-powered search. Returns semantically relevant results with content.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query',
},
},
required: ['query'],
},
execute: async (input: ExaSearchInput, _context?: ToolExecutionContext): Promise<ExaSearchResult> => {
const pkg = await loadExaPackage();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tool: any = pkg.webSearch(config);
if (tool && typeof tool.execute === 'function') {
const result = await tool.execute(input, { toolCallId: 'exa-search', messages: [] });
return result as ExaSearchResult;
}
return { results: [] };
},
};
}
/**
* Factory function for registry
*/
export function createExaSearchTool(config?: ExaSearchConfig): PraisonTool<unknown, unknown> {
return exaSearch(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,187 @@
/**
* Firecrawl Tool
*
* Web scraping, crawling, and content extraction.
* Package: @mendable/firecrawl-js
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError, MissingEnvVarError } from '../registry/types';
export const FIRECRAWL_METADATA: ToolMetadata = {
id: 'firecrawl',
displayName: 'Firecrawl',
description: 'Web scraping, crawling, and content extraction for LLM-ready data',
tags: ['scrape', 'crawl', 'extract', 'web'],
requiredEnv: ['FIRECRAWL_API_KEY'],
optionalEnv: [],
install: {
npm: 'npm install @mendable/firecrawl-js',
pnpm: 'pnpm add @mendable/firecrawl-js',
yarn: 'yarn add @mendable/firecrawl-js',
bun: 'bun add @mendable/firecrawl-js',
},
docsSlug: 'tools/firecrawl',
capabilities: {
crawl: true,
extract: true,
},
packageName: '@mendable/firecrawl-js',
};
export interface FirecrawlScrapeConfig {
formats?: ('markdown' | 'html' | 'rawHtml' | 'links' | 'screenshot')[];
onlyMainContent?: boolean;
includeTags?: string[];
excludeTags?: string[];
waitFor?: number;
}
export interface FirecrawlCrawlConfig {
limit?: number;
maxDepth?: number;
includePaths?: string[];
excludePaths?: string[];
allowBackwardLinks?: boolean;
allowExternalLinks?: boolean;
}
export interface FirecrawlScrapeInput {
url: string;
}
export interface FirecrawlCrawlInput {
url: string;
}
export interface FirecrawlScrapeResult {
content: string;
markdown?: string;
html?: string;
links?: string[];
metadata?: Record<string, unknown>;
}
export interface FirecrawlCrawlResult {
pages: Array<{
url: string;
content: string;
markdown?: string;
}>;
}
async function loadFirecrawlPackage() {
if (!process.env.FIRECRAWL_API_KEY) {
throw new MissingEnvVarError(
FIRECRAWL_METADATA.id,
'FIRECRAWL_API_KEY',
FIRECRAWL_METADATA.docsSlug
);
}
try {
return await import('@mendable/firecrawl-js');
} catch {
throw new MissingDependencyError(
FIRECRAWL_METADATA.id,
FIRECRAWL_METADATA.packageName,
FIRECRAWL_METADATA.install,
FIRECRAWL_METADATA.requiredEnv,
FIRECRAWL_METADATA.docsSlug
);
}
}
/**
* Create a Firecrawl Scrape tool
*/
export function firecrawlScrape(config?: FirecrawlScrapeConfig): PraisonTool<FirecrawlScrapeInput, FirecrawlScrapeResult> {
return {
name: 'firecrawlScrape',
description: 'Scrape a single web page and extract its content in LLM-ready format.',
parameters: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The URL to scrape',
},
},
required: ['url'],
},
execute: async (input: FirecrawlScrapeInput, context?: ToolExecutionContext): Promise<FirecrawlScrapeResult> => {
const pkg = await loadFirecrawlPackage();
// Firecrawl uses a class-based API
const FirecrawlApp = (pkg as Record<string, unknown>).default || pkg;
if (typeof FirecrawlApp === 'function') {
const app = new (FirecrawlApp as new (options: { apiKey: string }) => {
scrapeUrl: (url: string, options?: FirecrawlScrapeConfig) => Promise<{ data?: { markdown?: string; html?: string; links?: string[]; metadata?: Record<string, unknown> } }>;
})({ apiKey: process.env.FIRECRAWL_API_KEY! });
const result = await app.scrapeUrl(input.url, config);
return {
content: result.data?.markdown || '',
markdown: result.data?.markdown,
html: result.data?.html,
links: result.data?.links,
metadata: result.data?.metadata,
};
}
return { content: '' };
},
};
}
/**
* Create a Firecrawl Crawl tool
*/
export function firecrawlCrawl(config?: FirecrawlCrawlConfig): PraisonTool<FirecrawlCrawlInput, FirecrawlCrawlResult> {
return {
name: 'firecrawlCrawl',
description: 'Crawl a website and extract content from multiple pages.',
parameters: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The starting URL to crawl',
},
},
required: ['url'],
},
execute: async (input: FirecrawlCrawlInput, context?: ToolExecutionContext): Promise<FirecrawlCrawlResult> => {
const pkg = await loadFirecrawlPackage();
const FirecrawlApp = (pkg as Record<string, unknown>).default || pkg;
if (typeof FirecrawlApp === 'function') {
const app = new (FirecrawlApp as new (options: { apiKey: string }) => {
crawlUrl: (url: string, options?: FirecrawlCrawlConfig) => Promise<{ data?: Array<{ url: string; markdown?: string }> }>;
})({ apiKey: process.env.FIRECRAWL_API_KEY! });
const result = await app.crawlUrl(input.url, config);
return {
pages: (result.data || []).map(page => ({
url: page.url,
content: page.markdown || '',
markdown: page.markdown,
})),
};
}
return { pages: [] };
},
};
}
/**
* Factory functions for registry
*/
export function createFirecrawlScrapeTool(config?: FirecrawlScrapeConfig): PraisonTool<unknown, unknown> {
return firecrawlScrape(config) as PraisonTool<unknown, unknown>;
}
export function createFirecrawlCrawlTool(config?: FirecrawlCrawlConfig): PraisonTool<unknown, unknown> {
return firecrawlCrawl(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,112 @@
/**
* Built-in Tools - Main Exports
*
* All built-in tool adapters with lazy loading.
*/
// Tool metadata exports
export { CODE_EXECUTION_METADATA } from './code-execution';
export { TAVILY_METADATA } from './tavily';
export { EXA_METADATA } from './exa';
export { PERPLEXITY_METADATA } from './perplexity';
export { PARALLEL_METADATA } from './parallel';
export { FIRECRAWL_METADATA } from './firecrawl';
export { SUPERAGENT_METADATA } from './superagent';
export { VALYU_METADATA } from './valyu';
export { BEDROCK_AGENTCORE_METADATA } from './bedrock-agentcore';
export { AIRWEAVE_METADATA } from './airweave';
export { CODE_MODE_METADATA } from './code-mode';
// Code Execution
export { codeExecution, createCodeExecutionTool } from './code-execution';
export type { CodeExecutionConfig, CodeExecutionInput, CodeExecutionOutput } from './code-execution';
// Tavily
export { tavilySearch, tavilyExtract, tavilyCrawl, createTavilySearchTool, createTavilyExtractTool, createTavilyCrawlTool } from './tavily';
export type { TavilySearchConfig, TavilySearchInput, TavilySearchResult, TavilyExtractConfig, TavilyExtractInput, TavilyExtractResult, TavilyCrawlConfig, TavilyCrawlInput, TavilyCrawlResult } from './tavily';
// Exa
export { exaSearch, createExaSearchTool } from './exa';
export type { ExaSearchConfig, ExaSearchInput, ExaSearchResult } from './exa';
// Perplexity
export { perplexitySearch, createPerplexitySearchTool } from './perplexity';
export type { PerplexitySearchConfig, PerplexitySearchInput, PerplexitySearchResult } from './perplexity';
// Parallel
export { parallelSearch, createParallelSearchTool } from './parallel';
export type { ParallelSearchConfig, ParallelSearchInput, ParallelSearchResult } from './parallel';
// Firecrawl
export { firecrawlScrape, firecrawlCrawl, createFirecrawlScrapeTool, createFirecrawlCrawlTool } from './firecrawl';
export type { FirecrawlScrapeConfig, FirecrawlScrapeInput, FirecrawlScrapeResult, FirecrawlCrawlConfig, FirecrawlCrawlInput, FirecrawlCrawlResult } from './firecrawl';
// Superagent
export { superagentGuard, superagentRedact, superagentVerify, createSuperagentGuardTool, createSuperagentRedactTool, createSuperagentVerifyTool } from './superagent';
export type { GuardConfig, GuardInput, GuardResult, RedactConfig, RedactInput, RedactResult, VerifyConfig, VerifyInput, VerifyResult } from './superagent';
// Valyu
export {
valyuWebSearch, valyuFinanceSearch, valyuPaperSearch, valyuBioSearch,
valyuPatentSearch, valyuSecSearch, valyuEconomicsSearch, valyuCompanyResearch,
createValyuWebSearchTool, createValyuFinanceSearchTool, createValyuPaperSearchTool,
createValyuBioSearchTool, createValyuPatentSearchTool, createValyuSecSearchTool,
createValyuEconomicsSearchTool, createValyuCompanyResearchTool
} from './valyu';
export type { ValyuSearchConfig, ValyuSearchInput, ValyuSearchResult } from './valyu';
// Bedrock AgentCore
export {
bedrockCodeInterpreter, bedrockBrowserNavigate, bedrockBrowserClick, bedrockBrowserFill,
createBedrockCodeInterpreterTool, createBedrockBrowserNavigateTool,
createBedrockBrowserClickTool, createBedrockBrowserFillTool
} from './bedrock-agentcore';
export type { CodeInterpreterConfig, CodeInterpreterInput, CodeInterpreterResult, BrowserConfig, BrowserNavigateInput, BrowserClickInput, BrowserFillInput, BrowserResult } from './bedrock-agentcore';
// Airweave
export { airweaveSearch, createAirweaveSearchTool } from './airweave';
export type { AirweaveSearchConfig, AirweaveSearchInput, AirweaveSearchResult } from './airweave';
// Code Mode
export { codeMode, createCodeModeTool } from './code-mode';
export type { CodeModeConfig, CodeModeInput, CodeModeResult } from './code-mode';
// Custom Tools
export { registerCustomTool, createCustomTool, registerNpmTool, registerLocalTool, Tool } from './custom';
export type { CustomToolConfig } from './custom';
// All metadata for registration
export const ALL_BUILTIN_METADATA = [
// Lazy import to avoid loading all modules at once
] as const;
/**
* Get all built-in tool metadata
*/
export async function getAllBuiltinMetadata() {
const { CODE_EXECUTION_METADATA } = await import('./code-execution');
const { TAVILY_METADATA } = await import('./tavily');
const { EXA_METADATA } = await import('./exa');
const { PERPLEXITY_METADATA } = await import('./perplexity');
const { PARALLEL_METADATA } = await import('./parallel');
const { FIRECRAWL_METADATA } = await import('./firecrawl');
const { SUPERAGENT_METADATA } = await import('./superagent');
const { VALYU_METADATA } = await import('./valyu');
const { BEDROCK_AGENTCORE_METADATA } = await import('./bedrock-agentcore');
const { AIRWEAVE_METADATA } = await import('./airweave');
const { CODE_MODE_METADATA } = await import('./code-mode');
return [
CODE_EXECUTION_METADATA,
TAVILY_METADATA,
EXA_METADATA,
PERPLEXITY_METADATA,
PARALLEL_METADATA,
FIRECRAWL_METADATA,
SUPERAGENT_METADATA,
VALYU_METADATA,
BEDROCK_AGENTCORE_METADATA,
AIRWEAVE_METADATA,
CODE_MODE_METADATA,
];
}
@@ -0,0 +1,110 @@
/**
* Parallel Web Search Tool
*
* Web search with extraction and ctx-zip capabilities.
* Package: @parallel-web/ai-sdk-tools
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError, MissingEnvVarError } from '../registry/types';
export const PARALLEL_METADATA: ToolMetadata = {
id: 'parallel',
displayName: 'Parallel Web Search',
description: 'Web search with extraction and context compression capabilities',
tags: ['search', 'web', 'extraction'],
requiredEnv: ['PARALLEL_API_KEY'],
optionalEnv: [],
install: {
npm: 'npm install @parallel-web/ai-sdk-tools',
pnpm: 'pnpm add @parallel-web/ai-sdk-tools',
yarn: 'yarn add @parallel-web/ai-sdk-tools',
bun: 'bun add @parallel-web/ai-sdk-tools',
},
docsSlug: 'tools/parallel',
capabilities: {
search: true,
extract: true,
},
packageName: '@parallel-web/ai-sdk-tools',
};
export interface ParallelSearchConfig {
maxResults?: number;
includeContent?: boolean;
}
export interface ParallelSearchInput {
query: string;
}
export interface ParallelSearchResult {
results: Array<{
title: string;
url: string;
content?: string;
}>;
}
async function loadParallelPackage() {
if (!process.env.PARALLEL_API_KEY) {
throw new MissingEnvVarError(
PARALLEL_METADATA.id,
'PARALLEL_API_KEY',
PARALLEL_METADATA.docsSlug
);
}
try {
// @ts-ignore - optional dependency
return await import('@parallel-web/ai-sdk-tools');
} catch {
throw new MissingDependencyError(
PARALLEL_METADATA.id,
PARALLEL_METADATA.packageName,
PARALLEL_METADATA.install,
PARALLEL_METADATA.requiredEnv,
PARALLEL_METADATA.docsSlug
);
}
}
/**
* Create a Parallel Search tool
*/
export function parallelSearch(config?: ParallelSearchConfig): PraisonTool<ParallelSearchInput, ParallelSearchResult> {
return {
name: 'parallelSearch',
description: 'Search the web using Parallel with content extraction capabilities.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query',
},
},
required: ['query'],
},
execute: async (input: ParallelSearchInput, context?: ToolExecutionContext): Promise<ParallelSearchResult> => {
const pkg = await loadParallelPackage();
const searchFn = (pkg as Record<string, unknown>).parallelSearch || (pkg as Record<string, unknown>).search;
if (searchFn && typeof searchFn === 'function') {
const tool = searchFn(config);
if (tool && typeof tool.execute === 'function') {
return await tool.execute(input);
}
}
return { results: [] };
},
};
}
/**
* Factory function for registry
*/
export function createParallelSearchTool(config?: ParallelSearchConfig): PraisonTool<unknown, unknown> {
return parallelSearch(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,112 @@
/**
* Perplexity Search Tool
*
* Web search with real-time results powered by Perplexity.
* Package: @perplexity-ai/ai-sdk
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError, MissingEnvVarError } from '../registry/types';
export const PERPLEXITY_METADATA: ToolMetadata = {
id: 'perplexity',
displayName: 'Perplexity Search',
description: 'Web search with real-time results and advanced filtering powered by Perplexity',
tags: ['search', 'web', 'realtime'],
requiredEnv: ['PERPLEXITY_API_KEY'],
optionalEnv: [],
install: {
npm: 'npm install @perplexity-ai/ai-sdk',
pnpm: 'pnpm add @perplexity-ai/ai-sdk',
yarn: 'yarn add @perplexity-ai/ai-sdk',
bun: 'bun add @perplexity-ai/ai-sdk',
},
docsSlug: 'tools/perplexity',
capabilities: {
search: true,
},
packageName: '@perplexity-ai/ai-sdk',
};
export interface PerplexitySearchConfig {
maxResults?: number;
recencyFilter?: 'hour' | 'day' | 'week' | 'month' | 'year';
domainFilter?: string[];
}
export interface PerplexitySearchInput {
query: string;
}
export interface PerplexitySearchResult {
results: Array<{
title: string;
url: string;
snippet: string;
}>;
answer?: string;
}
async function loadPerplexityPackage() {
if (!process.env.PERPLEXITY_API_KEY) {
throw new MissingEnvVarError(
PERPLEXITY_METADATA.id,
'PERPLEXITY_API_KEY',
PERPLEXITY_METADATA.docsSlug
);
}
try {
// @ts-ignore - optional dependency
return await import('@perplexity-ai/ai-sdk');
} catch {
throw new MissingDependencyError(
PERPLEXITY_METADATA.id,
PERPLEXITY_METADATA.packageName,
PERPLEXITY_METADATA.install,
PERPLEXITY_METADATA.requiredEnv,
PERPLEXITY_METADATA.docsSlug
);
}
}
/**
* Create a Perplexity Search tool
*/
export function perplexitySearch(config?: PerplexitySearchConfig): PraisonTool<PerplexitySearchInput, PerplexitySearchResult> {
return {
name: 'perplexitySearch',
description: 'Search the web using Perplexity for real-time information and answers.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query',
},
},
required: ['query'],
},
execute: async (input: PerplexitySearchInput, context?: ToolExecutionContext): Promise<PerplexitySearchResult> => {
const pkg = await loadPerplexityPackage();
// Check for the search function
const searchFn = (pkg as Record<string, unknown>).perplexitySearch || (pkg as Record<string, unknown>).search;
if (searchFn && typeof searchFn === 'function') {
const tool = searchFn(config);
if (tool && typeof tool.execute === 'function') {
return await tool.execute(input);
}
}
return { results: [] };
},
};
}
/**
* Factory function for registry
*/
export function createPerplexitySearchTool(config?: PerplexitySearchConfig): PraisonTool<unknown, unknown> {
return perplexitySearch(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,223 @@
/**
* Superagent Security Tools
*
* AI security guardrails: Guard, Redact, Verify.
* Package: @superagent-ai/ai-sdk
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError, MissingEnvVarError } from '../registry/types';
export const SUPERAGENT_METADATA: ToolMetadata = {
id: 'superagent',
displayName: 'Superagent Security',
description: 'AI security guardrails: prompt injection protection, PII/PHI redaction, claim verification',
tags: ['security', 'guardrails', 'pii', 'prompt-injection', 'verification'],
requiredEnv: ['SUPERAGENT_API_KEY'],
optionalEnv: [],
install: {
npm: 'npm install @superagent-ai/ai-sdk',
pnpm: 'pnpm add @superagent-ai/ai-sdk',
yarn: 'yarn add @superagent-ai/ai-sdk',
bun: 'bun add @superagent-ai/ai-sdk',
},
docsSlug: 'tools/superagent',
capabilities: {
security: true,
},
packageName: '@superagent-ai/ai-sdk',
};
// Guard types
export interface GuardConfig {
strictMode?: boolean;
}
export interface GuardInput {
text: string;
}
export interface GuardResult {
safe: boolean;
threats: string[];
decision: {
status: 'allow' | 'block';
violationTypes?: string[];
};
}
// Redact types
export interface RedactConfig {
entities?: string[];
}
export interface RedactInput {
text: string;
}
export interface RedactResult {
redactedText: string;
entities: Array<{
type: string;
original: string;
replacement: string;
}>;
}
// Verify types
export interface VerifyConfig {
strictMode?: boolean;
}
export interface VerifyInput {
claim: string;
sources: string[];
}
export interface VerifyResult {
verified: boolean;
confidence: number;
evidence: string[];
}
async function loadSuperagentPackage() {
if (!process.env.SUPERAGENT_API_KEY) {
throw new MissingEnvVarError(
SUPERAGENT_METADATA.id,
'SUPERAGENT_API_KEY',
SUPERAGENT_METADATA.docsSlug
);
}
try {
// @ts-ignore - Optional dependency
return await import('@superagent-ai/ai-sdk');
} catch {
throw new MissingDependencyError(
SUPERAGENT_METADATA.id,
SUPERAGENT_METADATA.packageName,
SUPERAGENT_METADATA.install,
SUPERAGENT_METADATA.requiredEnv,
SUPERAGENT_METADATA.docsSlug
);
}
}
/**
* Create a Superagent Guard tool
*/
export function superagentGuard(config?: GuardConfig): PraisonTool<GuardInput, GuardResult> {
return {
name: 'guard',
description: 'Check text for security threats like prompt injection, jailbreaks, and data exfiltration attempts.',
parameters: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'The text to analyze for security threats',
},
},
required: ['text'],
},
execute: async (input: GuardInput, context?: ToolExecutionContext): Promise<GuardResult> => {
const pkg = await loadSuperagentPackage();
const guardFn = (pkg as Record<string, unknown>).guard;
if (guardFn && typeof guardFn === 'function') {
const tool = guardFn(config);
if (tool && typeof tool.execute === 'function') {
return await tool.execute(input);
}
}
return { safe: true, threats: [], decision: { status: 'allow' } };
},
};
}
/**
* Create a Superagent Redact tool
*/
export function superagentRedact(config?: RedactConfig): PraisonTool<RedactInput, RedactResult> {
return {
name: 'redact',
description: 'Remove sensitive data (PII, PHI, secrets) from text. Redacts SSNs, emails, phone numbers, credit cards, etc.',
parameters: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'The text to redact sensitive information from',
},
},
required: ['text'],
},
execute: async (input: RedactInput, context?: ToolExecutionContext): Promise<RedactResult> => {
const pkg = await loadSuperagentPackage();
const redactFn = (pkg as Record<string, unknown>).redact;
if (redactFn && typeof redactFn === 'function') {
const tool = redactFn(config);
if (tool && typeof tool.execute === 'function') {
return await tool.execute(input);
}
}
return { redactedText: input.text, entities: [] };
},
};
}
/**
* Create a Superagent Verify tool
*/
export function superagentVerify(config?: VerifyConfig): PraisonTool<VerifyInput, VerifyResult> {
return {
name: 'verify',
description: 'Verify claims against source materials. Check if statements are supported by provided evidence.',
parameters: {
type: 'object',
properties: {
claim: {
type: 'string',
description: 'The claim to verify',
},
sources: {
type: 'array',
description: 'Source materials to verify against',
items: { type: 'string' },
},
},
required: ['claim', 'sources'],
},
execute: async (input: VerifyInput, context?: ToolExecutionContext): Promise<VerifyResult> => {
const pkg = await loadSuperagentPackage();
const verifyFn = (pkg as Record<string, unknown>).verify;
if (verifyFn && typeof verifyFn === 'function') {
const tool = verifyFn(config);
if (tool && typeof tool.execute === 'function') {
return await tool.execute(input);
}
}
return { verified: false, confidence: 0, evidence: [] };
},
};
}
/**
* Factory functions for registry
*/
export function createSuperagentGuardTool(config?: GuardConfig): PraisonTool<unknown, unknown> {
return superagentGuard(config) as PraisonTool<unknown, unknown>;
}
export function createSuperagentRedactTool(config?: RedactConfig): PraisonTool<unknown, unknown> {
return superagentRedact(config) as PraisonTool<unknown, unknown>;
}
export function createSuperagentVerifyTool(config?: VerifyConfig): PraisonTool<unknown, unknown> {
return superagentVerify(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,361 @@
/**
* Tavily Tools (Search, Extract, Crawl, Map)
*
* Web search, content extraction, crawling, and site mapping.
* Package: @tavily/ai-sdk (uses @tavily/core internally)
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError, MissingEnvVarError } from '../registry/types';
export const TAVILY_METADATA: ToolMetadata = {
id: 'tavily',
displayName: 'Tavily',
description: 'Web search, content extraction, crawling, and site mapping powered by Tavily',
tags: ['search', 'web', 'extract', 'crawl'],
requiredEnv: ['TAVILY_API_KEY'],
optionalEnv: [],
install: {
npm: 'npm install @tavily/ai-sdk',
pnpm: 'pnpm add @tavily/ai-sdk',
yarn: 'yarn add @tavily/ai-sdk',
bun: 'bun add @tavily/ai-sdk',
},
docsSlug: 'tools/tavily',
capabilities: {
search: true,
extract: true,
crawl: true,
},
packageName: '@tavily/ai-sdk',
};
// Search types
export interface TavilySearchConfig {
apiKey?: string;
searchDepth?: 'basic' | 'advanced';
topic?: 'general' | 'news' | 'finance';
includeAnswer?: boolean;
maxResults?: number;
includeImages?: boolean;
timeRange?: 'year' | 'month' | 'week' | 'day';
includeDomains?: string[];
excludeDomains?: string[];
}
export interface TavilySearchInput {
query: string;
searchDepth?: 'basic' | 'advanced';
timeRange?: 'year' | 'month' | 'week' | 'day';
}
export interface TavilySearchResult {
results: Array<{
title: string;
url: string;
content: string;
score?: number;
}>;
answer?: string;
images?: Array<{ url: string; description?: string }>;
query?: string;
responseTime?: number;
}
// Extract types
export interface TavilyExtractConfig {
apiKey?: string;
extractDepth?: 'basic' | 'advanced';
}
export interface TavilyExtractInput {
urls: string[];
extractDepth?: 'basic' | 'advanced';
query?: string;
}
export interface TavilyExtractResult {
results: Array<{
url: string;
rawContent: string;
}>;
}
// Crawl types
export interface TavilyCrawlConfig {
apiKey?: string;
maxDepth?: number;
extractDepth?: 'basic' | 'advanced';
instructions?: string;
allowExternal?: boolean;
}
export interface TavilyCrawlInput {
url: string;
maxDepth?: number;
extractDepth?: 'basic' | 'advanced';
instructions?: string;
allowExternal?: boolean;
}
export interface TavilyCrawlResult {
baseUrl: string;
results: Array<{
url: string;
rawContent: string;
}>;
}
// Helper to check env and load package
async function loadTavilyPackage() {
if (!process.env.TAVILY_API_KEY) {
throw new MissingEnvVarError(
TAVILY_METADATA.id,
'TAVILY_API_KEY',
TAVILY_METADATA.docsSlug
);
}
try {
// @ts-ignore - optional dependency
return await import('@tavily/ai-sdk');
} catch {
throw new MissingDependencyError(
TAVILY_METADATA.id,
TAVILY_METADATA.packageName,
TAVILY_METADATA.install,
TAVILY_METADATA.requiredEnv,
TAVILY_METADATA.docsSlug
);
}
}
/**
* Create a Tavily Search tool
* Returns a PraisonTool that can be used with agents or directly
*/
export function tavilySearch(config?: TavilySearchConfig): PraisonTool<TavilySearchInput, TavilySearchResult> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let cachedTool: any = null;
return {
name: 'tavilySearch',
description: 'Search the web for information using Tavily. Returns relevant results with content snippets and optional AI-generated answers.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query',
},
searchDepth: {
type: 'string',
description: 'Search depth: basic or advanced',
enum: ['basic', 'advanced'],
},
timeRange: {
type: 'string',
description: 'Time range filter: year, month, week, or day',
enum: ['year', 'month', 'week', 'day'],
},
},
required: ['query'],
},
execute: async (input: TavilySearchInput, _context?: ToolExecutionContext): Promise<TavilySearchResult> => {
const pkg = await loadTavilyPackage();
// Create the AI SDK tool with config
if (!cachedTool) {
cachedTool = pkg.tavilySearch({
apiKey: config?.apiKey || process.env.TAVILY_API_KEY,
searchDepth: config?.searchDepth,
topic: config?.topic,
includeAnswer: config?.includeAnswer,
maxResults: config?.maxResults,
includeImages: config?.includeImages,
timeRange: config?.timeRange,
includeDomains: config?.includeDomains,
excludeDomains: config?.excludeDomains,
});
}
// AI SDK tools have execute method - call with required params
const result = await cachedTool.execute(
{ query: input.query, searchDepth: input.searchDepth, timeRange: input.timeRange },
{ toolCallId: 'tavily-search', messages: [] }
);
// Transform to our result type
return {
results: (result.results || []).map((r: { title: string; url: string; content: string; score?: number }) => ({
title: r.title,
url: r.url,
content: r.content,
score: r.score,
})),
answer: result.answer,
images: result.images,
query: result.query,
responseTime: result.responseTime,
};
},
};
}
/**
* Create a Tavily Extract tool
*/
export function tavilyExtract(config?: TavilyExtractConfig): PraisonTool<TavilyExtractInput, TavilyExtractResult> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let cachedTool: any = null;
return {
name: 'tavilyExtract',
description: 'Extract structured content from web pages using Tavily. Provide URLs to extract their content.',
parameters: {
type: 'object',
properties: {
urls: {
type: 'array',
description: 'URLs to extract content from',
items: { type: 'string' },
},
extractDepth: {
type: 'string',
description: 'Extraction depth: basic or advanced',
enum: ['basic', 'advanced'],
},
query: {
type: 'string',
description: 'Optional query to focus extraction',
},
},
required: ['urls'],
},
execute: async (input: TavilyExtractInput, _context?: ToolExecutionContext): Promise<TavilyExtractResult> => {
const pkg = await loadTavilyPackage();
if (!cachedTool) {
cachedTool = pkg.tavilyExtract({
apiKey: config?.apiKey || process.env.TAVILY_API_KEY,
extractDepth: config?.extractDepth,
});
}
const result = await cachedTool.execute(
{ urls: input.urls, extractDepth: input.extractDepth, query: input.query },
{ toolCallId: 'tavily-extract', messages: [] }
);
return {
results: (result.results || []).map((r: { url: string; rawContent: string }) => ({
url: r.url,
rawContent: r.rawContent,
})),
};
},
};
}
/**
* Create a Tavily Crawl tool
*/
export function tavilyCrawl(config?: TavilyCrawlConfig): PraisonTool<TavilyCrawlInput, TavilyCrawlResult> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let cachedTool: any = null;
return {
name: 'tavilyCrawl',
description: 'Crawl a website using Tavily. Discovers and extracts content from multiple pages starting from a base URL.',
parameters: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The starting URL to crawl',
},
maxDepth: {
type: 'number',
description: 'Maximum crawl depth',
},
extractDepth: {
type: 'string',
description: 'Extraction depth: basic or advanced',
enum: ['basic', 'advanced'],
},
instructions: {
type: 'string',
description: 'Instructions for the crawler',
},
allowExternal: {
type: 'boolean',
description: 'Allow crawling external links',
},
},
required: ['url'],
},
execute: async (input: TavilyCrawlInput, _context?: ToolExecutionContext): Promise<TavilyCrawlResult> => {
const pkg = await loadTavilyPackage();
if (!cachedTool) {
cachedTool = pkg.tavilyCrawl({
apiKey: config?.apiKey || process.env.TAVILY_API_KEY,
maxDepth: config?.maxDepth,
extractDepth: config?.extractDepth,
instructions: config?.instructions,
allowExternal: config?.allowExternal,
});
}
const result = await cachedTool.execute(
{ url: input.url, maxDepth: input.maxDepth, extractDepth: input.extractDepth, instructions: input.instructions, allowExternal: input.allowExternal },
{ toolCallId: 'tavily-crawl', messages: [] }
);
return {
baseUrl: result.baseUrl || input.url,
results: (result.results || []).map((r: { url: string; rawContent: string }) => ({
url: r.url,
rawContent: r.rawContent,
})),
};
},
};
}
/**
* Get the raw AI SDK tool for use with generateText
* This returns the native @tavily/ai-sdk tool
*/
export async function getTavilyAISDKTool(type: 'search' | 'extract' | 'crawl' | 'map' = 'search', config?: TavilySearchConfig) {
const pkg = await loadTavilyPackage();
const apiKey = config?.apiKey || process.env.TAVILY_API_KEY;
switch (type) {
case 'search':
return pkg.tavilySearch({ apiKey, searchDepth: config?.searchDepth, topic: config?.topic });
case 'extract':
return pkg.tavilyExtract({ apiKey });
case 'crawl':
return pkg.tavilyCrawl({ apiKey });
case 'map':
return pkg.tavilyMap({ apiKey });
default:
return pkg.tavilySearch({ apiKey });
}
}
/**
* Factory functions for registry
*/
export function createTavilySearchTool(config?: TavilySearchConfig): PraisonTool<unknown, unknown> {
return tavilySearch(config) as PraisonTool<unknown, unknown>;
}
export function createTavilyExtractTool(config?: TavilyExtractConfig): PraisonTool<unknown, unknown> {
return tavilyExtract(config) as PraisonTool<unknown, unknown>;
}
export function createTavilyCrawlTool(config?: TavilyCrawlConfig): PraisonTool<unknown, unknown> {
return tavilyCrawl(config) as PraisonTool<unknown, unknown>;
}
@@ -0,0 +1,236 @@
/**
* Valyu Domain Search Tools
*
* Specialized search for finance, papers, bio, patents, SEC, economics, and company research.
* Package: @valyu/ai-sdk
*/
import type { ToolMetadata, PraisonTool, ToolExecutionContext } from '../registry/types';
import { MissingDependencyError, MissingEnvVarError } from '../registry/types';
export const VALYU_METADATA: ToolMetadata = {
id: 'valyu',
displayName: 'Valyu Domain Search',
description: 'Specialized search for finance, academic papers, bio, patents, SEC filings, economics, and company research',
tags: ['search', 'finance', 'papers', 'patents', 'sec', 'economics', 'research'],
requiredEnv: ['VALYU_API_KEY'],
optionalEnv: [],
install: {
npm: 'npm install @valyu/ai-sdk',
pnpm: 'pnpm add @valyu/ai-sdk',
yarn: 'yarn add @valyu/ai-sdk',
bun: 'bun add @valyu/ai-sdk',
},
docsSlug: 'tools/valyu',
capabilities: {
search: true,
},
packageName: '@valyu/ai-sdk',
};
export interface ValyuSearchConfig {
maxNumResults?: number;
}
export interface ValyuSearchInput {
query: string;
}
export interface ValyuSearchResult {
results: Array<{
title: string;
url: string;
content: string;
source?: string;
}>;
}
async function loadValyuPackage() {
if (!process.env.VALYU_API_KEY) {
throw new MissingEnvVarError(
VALYU_METADATA.id,
'VALYU_API_KEY',
VALYU_METADATA.docsSlug
);
}
try {
// @ts-ignore - optional dependency
return await import('@valyu/ai-sdk');
} catch {
throw new MissingDependencyError(
VALYU_METADATA.id,
VALYU_METADATA.packageName,
VALYU_METADATA.install,
VALYU_METADATA.requiredEnv,
VALYU_METADATA.docsSlug
);
}
}
// Helper to create a Valyu search tool
function createValyuTool(
name: string,
description: string,
searchFnName: string,
config?: ValyuSearchConfig
): PraisonTool<ValyuSearchInput, ValyuSearchResult> {
return {
name,
description,
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query',
},
},
required: ['query'],
},
execute: async (input: ValyuSearchInput, context?: ToolExecutionContext): Promise<ValyuSearchResult> => {
const pkg = await loadValyuPackage();
const searchFn = (pkg as Record<string, unknown>)[searchFnName];
if (searchFn && typeof searchFn === 'function') {
const tool = searchFn(config);
if (tool && typeof tool.execute === 'function') {
return await tool.execute(input);
}
}
return { results: [] };
},
};
}
/**
* Create a Valyu Web Search tool
*/
export function valyuWebSearch(config?: ValyuSearchConfig): PraisonTool<ValyuSearchInput, ValyuSearchResult> {
return createValyuTool(
'valyuWebSearch',
'Search the web using Valyu for general information.',
'webSearch',
config
);
}
/**
* Create a Valyu Finance Search tool
*/
export function valyuFinanceSearch(config?: ValyuSearchConfig): PraisonTool<ValyuSearchInput, ValyuSearchResult> {
return createValyuTool(
'valyuFinanceSearch',
'Search financial data including stock prices, earnings, insider transactions, dividends, balance sheets, and income statements.',
'financeSearch',
config
);
}
/**
* Create a Valyu Paper Search tool
*/
export function valyuPaperSearch(config?: ValyuSearchConfig): PraisonTool<ValyuSearchInput, ValyuSearchResult> {
return createValyuTool(
'valyuPaperSearch',
'Search academic papers from PubMed, arXiv, bioRxiv, medRxiv, and other scholarly sources.',
'paperSearch',
config
);
}
/**
* Create a Valyu Bio Search tool
*/
export function valyuBioSearch(config?: ValyuSearchConfig): PraisonTool<ValyuSearchInput, ValyuSearchResult> {
return createValyuTool(
'valyuBioSearch',
'Search clinical trials, FDA drug labels, and peer-reviewed biomedical research.',
'bioSearch',
config
);
}
/**
* Create a Valyu Patent Search tool
*/
export function valyuPatentSearch(config?: ValyuSearchConfig): PraisonTool<ValyuSearchInput, ValyuSearchResult> {
return createValyuTool(
'valyuPatentSearch',
'Search USPTO full-text patents and related intellectual property.',
'patentSearch',
config
);
}
/**
* Create a Valyu SEC Search tool
*/
export function valyuSecSearch(config?: ValyuSearchConfig): PraisonTool<ValyuSearchInput, ValyuSearchResult> {
return createValyuTool(
'valyuSecSearch',
'Search SEC filings including 10-K, 10-Q, 8-K, and regulatory disclosures.',
'secSearch',
config
);
}
/**
* Create a Valyu Economics Search tool
*/
export function valyuEconomicsSearch(config?: ValyuSearchConfig): PraisonTool<ValyuSearchInput, ValyuSearchResult> {
return createValyuTool(
'valyuEconomicsSearch',
'Search economic indicators from BLS, FRED, World Bank, USAspending, and more.',
'economicsSearch',
config
);
}
/**
* Create a Valyu Company Research tool
*/
export function valyuCompanyResearch(config?: ValyuSearchConfig): PraisonTool<ValyuSearchInput, ValyuSearchResult> {
return createValyuTool(
'valyuCompanyResearch',
'Comprehensive company research and intelligence reports.',
'companyResearch',
config
);
}
/**
* Factory functions for registry
*/
export function createValyuWebSearchTool(config?: ValyuSearchConfig): PraisonTool<unknown, unknown> {
return valyuWebSearch(config) as PraisonTool<unknown, unknown>;
}
export function createValyuFinanceSearchTool(config?: ValyuSearchConfig): PraisonTool<unknown, unknown> {
return valyuFinanceSearch(config) as PraisonTool<unknown, unknown>;
}
export function createValyuPaperSearchTool(config?: ValyuSearchConfig): PraisonTool<unknown, unknown> {
return valyuPaperSearch(config) as PraisonTool<unknown, unknown>;
}
export function createValyuBioSearchTool(config?: ValyuSearchConfig): PraisonTool<unknown, unknown> {
return valyuBioSearch(config) as PraisonTool<unknown, unknown>;
}
export function createValyuPatentSearchTool(config?: ValyuSearchConfig): PraisonTool<unknown, unknown> {
return valyuPatentSearch(config) as PraisonTool<unknown, unknown>;
}
export function createValyuSecSearchTool(config?: ValyuSearchConfig): PraisonTool<unknown, unknown> {
return valyuSecSearch(config) as PraisonTool<unknown, unknown>;
}
export function createValyuEconomicsSearchTool(config?: ValyuSearchConfig): PraisonTool<unknown, unknown> {
return valyuEconomicsSearch(config) as PraisonTool<unknown, unknown>;
}
export function createValyuCompanyResearchTool(config?: ValyuSearchConfig): PraisonTool<unknown, unknown> {
return valyuCompanyResearch(config) as PraisonTool<unknown, unknown>;
}
+11 -1
View File
@@ -1,9 +1,19 @@
// Export base tool interfaces and classes
export { BaseTool, ToolResult, ToolValidationError, validateTool, createTool, type ToolParameters } from './base';
// Export decorator and registry
// Export decorator and registry (legacy)
export * from './decorator';
// Export all tool modules
export * from './arxivTools';
export * from './mcpSse';
// Export new registry system
export * from './registry';
// Export built-in tools
export * from './builtins';
// Export tools facade
export { tools, registerBuiltinTools } from './tools';
export type { default as ToolsFacade } from './tools';
@@ -0,0 +1,48 @@
/**
* AI SDK Tools Registry - Main Exports
*
* Lazy-loaded exports for the tools registry system.
*/
// Types
export type {
ToolExecutionContext,
ToolLimits,
RedactionHooks,
ToolLogger,
ToolCapabilities,
InstallHints,
ToolMetadata,
ToolExecutionResult,
PraisonTool,
ToolParameterSchema,
ToolParameterProperty,
ToolMiddleware,
ToolHooks,
ToolFactory,
RegisteredTool,
ToolInstallStatus,
} from './types';
// Errors
export { MissingDependencyError, MissingEnvVarError } from './types';
// Registry
export {
ToolsRegistry,
getToolsRegistry,
createToolsRegistry,
resetToolsRegistry,
} from './registry';
// Middleware
export {
createLoggingMiddleware,
createTimeoutMiddleware,
createRedactionMiddleware,
createRateLimitMiddleware,
createRetryMiddleware,
createTracingMiddleware,
createValidationMiddleware,
composeMiddleware,
} from './middleware';
@@ -0,0 +1,251 @@
/**
* AI SDK Tools Registry - Middleware Pipeline
*
* Composable middleware for tool execution:
* - Logging
* - Tracing
* - Rate limiting
* - Redaction
* - Timeout handling
*/
import type { ToolMiddleware, ToolExecutionContext, ToolLogger } from './types';
/**
* Create a logging middleware
*/
export function createLoggingMiddleware(logger?: ToolLogger): ToolMiddleware {
const log = logger || console;
return async (input, context, next) => {
const startTime = Date.now();
const traceId = context.traceId || 'unknown';
log.debug?.(`[${traceId}] Tool call started`, { input });
try {
const result = await next();
const duration = Date.now() - startTime;
log.debug?.(`[${traceId}] Tool call completed in ${duration}ms`);
return result;
} catch (error) {
const duration = Date.now() - startTime;
log.error?.(`[${traceId}] Tool call failed after ${duration}ms`, error);
throw error;
}
};
}
/**
* Create a timeout middleware
*/
export function createTimeoutMiddleware(defaultTimeoutMs: number = 30000): ToolMiddleware {
return async (input, context, next) => {
const timeoutMs = context.limits?.timeoutMs || defaultTimeoutMs;
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new Error(`Tool execution timed out after ${timeoutMs}ms`));
}, timeoutMs);
});
return Promise.race([next(), timeoutPromise]);
};
}
/**
* Create a redaction middleware for PII/sensitive data
*/
export function createRedactionMiddleware(patterns?: RegExp[]): ToolMiddleware {
const defaultPatterns = [
// Email
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
// SSN
/\b\d{3}-\d{2}-\d{4}\b/g,
// Credit card
/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g,
// Phone (US)
/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
];
const allPatterns = [...defaultPatterns, ...(patterns || [])];
const redact = (value: unknown): unknown => {
if (typeof value === 'string') {
let result = value;
for (const pattern of allPatterns) {
result = result.replace(pattern, '[REDACTED]');
}
return result;
}
if (Array.isArray(value)) {
return value.map(redact);
}
if (value && typeof value === 'object') {
const result: Record<string, unknown> = {};
for (const [k, v] of Object.entries(value)) {
result[k] = redact(v);
}
return result;
}
return value;
};
return async (input, context, next) => {
// Redact input if hooks are set
const redactedInput = context.redaction?.redactInput
? context.redaction.redactInput(input)
: redact(input);
// Execute with redacted input
const result = await next();
// Redact output if hooks are set
const redactedOutput = context.redaction?.redactOutput
? context.redaction.redactOutput(result)
: redact(result);
return redactedOutput;
};
}
/**
* Create a rate limiting middleware (simple token bucket)
*/
export function createRateLimitMiddleware(
maxRequests: number = 10,
windowMs: number = 1000
): ToolMiddleware {
const requests: number[] = [];
return async (input, context, next) => {
const now = Date.now();
// Remove old requests outside the window
while (requests.length > 0 && requests[0] < now - windowMs) {
requests.shift();
}
if (requests.length >= maxRequests) {
const waitTime = requests[0] + windowMs - now;
throw new Error(`Rate limit exceeded. Try again in ${waitTime}ms`);
}
requests.push(now);
return next();
};
}
/**
* Create a retry middleware with exponential backoff
*/
export function createRetryMiddleware(
maxRetries: number = 3,
baseDelayMs: number = 1000
): ToolMiddleware {
return async (input, context, next) => {
let lastError: Error | undefined;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await next();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (attempt < maxRetries) {
const delay = baseDelayMs * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
};
}
/**
* Create a tracing middleware that adds trace context
*/
export function createTracingMiddleware(): ToolMiddleware {
return async (input, context, next) => {
// Generate trace ID if not present
if (!context.traceId) {
context.traceId = generateTraceId();
}
// Add span for this tool call
const spanId = generateSpanId();
const startTime = Date.now();
try {
const result = await next();
// Log span completion
if (context.logger) {
context.logger.debug('Tool span completed', {
traceId: context.traceId,
spanId,
durationMs: Date.now() - startTime,
});
}
return result;
} catch (error) {
if (context.logger) {
context.logger.error('Tool span failed', {
traceId: context.traceId,
spanId,
durationMs: Date.now() - startTime,
error,
});
}
throw error;
}
};
}
/**
* Create a validation middleware that checks input against limits
*/
export function createValidationMiddleware(): ToolMiddleware {
return async (input, context, next) => {
const limits = context.limits;
if (limits?.maxPayloadBytes) {
const size = JSON.stringify(input).length;
if (size > limits.maxPayloadBytes) {
throw new Error(`Input payload size (${size} bytes) exceeds limit (${limits.maxPayloadBytes} bytes)`);
}
}
return next();
};
}
/**
* Compose multiple middleware into a single middleware
*/
export function composeMiddleware(...middlewares: ToolMiddleware[]): ToolMiddleware {
return async (input, context, next) => {
let index = 0;
const composedNext = async (): Promise<unknown> => {
if (index < middlewares.length) {
const mw = middlewares[index++];
return mw(input, context, composedNext);
}
return next();
};
return composedNext();
};
}
// Helper functions
function generateTraceId(): string {
return `trace_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 11)}`;
}
function generateSpanId(): string {
return `span_${Math.random().toString(36).slice(2, 11)}`;
}
@@ -0,0 +1,286 @@
/**
* AI SDK Tools Registry - Core Registry Implementation
*
* Singleton-safe registry for managing built-in and custom tools.
* Supports lazy loading, middleware, and multi-agent safety.
*/
import type {
ToolMetadata,
ToolFactory,
RegisteredTool,
PraisonTool,
ToolExecutionContext,
ToolHooks,
ToolMiddleware,
ToolInstallStatus,
MissingDependencyError,
MissingEnvVarError,
} from './types';
/**
* Tools Registry - Manages tool registration, lookup, and instantiation
*/
export class ToolsRegistry {
private tools: Map<string, RegisteredTool> = new Map();
private middleware: ToolMiddleware[] = [];
private hooks: ToolHooks = {};
private installCache: Map<string, boolean> = new Map();
/**
* Register a tool with the registry
*/
register(metadata: ToolMetadata, factory: ToolFactory): this {
this.tools.set(metadata.id, { metadata, factory });
return this;
}
/**
* Unregister a tool
*/
unregister(id: string): boolean {
return this.tools.delete(id);
}
/**
* Check if a tool is registered
*/
has(id: string): boolean {
return this.tools.has(id);
}
/**
* Get tool metadata
*/
getMetadata(id: string): ToolMetadata | undefined {
return this.tools.get(id)?.metadata;
}
/**
* List all registered tools
*/
list(): ToolMetadata[] {
return Array.from(this.tools.values()).map(t => t.metadata);
}
/**
* List tools by tag
*/
listByTag(tag: string): ToolMetadata[] {
return this.list().filter(t => t.tags.includes(tag));
}
/**
* List tools by capability
*/
listByCapability(capability: keyof ToolMetadata['capabilities']): ToolMetadata[] {
return this.list().filter(t => t.capabilities[capability]);
}
/**
* Create a tool instance (lazy loads the dependency)
*/
create<TConfig = unknown, TInput = unknown, TOutput = unknown>(
id: string,
config?: TConfig
): PraisonTool<TInput, TOutput> {
const registered = this.tools.get(id);
if (!registered) {
throw new Error(`Tool "${id}" is not registered. Available tools: ${this.list().map(t => t.id).join(', ')}`);
}
// Create the tool using the factory
const tool = registered.factory(config) as PraisonTool<TInput, TOutput>;
// Wrap execute with middleware and hooks
const originalExecute = tool.execute.bind(tool);
tool.execute = async (input: TInput, context?: ToolExecutionContext) => {
const ctx = context || {};
// Call beforeToolCall hook
if (this.hooks.beforeToolCall) {
await this.hooks.beforeToolCall(tool.name, input, ctx);
}
try {
// Build middleware chain
let result: unknown;
const executeWithMiddleware = async (): Promise<unknown> => {
return originalExecute(input, ctx);
};
if (this.middleware.length === 0) {
result = await executeWithMiddleware();
} else {
// Chain middleware
let index = 0;
const next = async (): Promise<unknown> => {
if (index < this.middleware.length) {
const mw = this.middleware[index++];
return mw(input, ctx, next);
}
return executeWithMiddleware();
};
result = await next();
}
// Call afterToolCall hook
if (this.hooks.afterToolCall) {
await this.hooks.afterToolCall(tool.name, input, result, ctx);
}
return result as TOutput;
} catch (error) {
// Call onError hook
if (this.hooks.onError && error instanceof Error) {
await this.hooks.onError(tool.name, error, ctx);
}
throw error;
}
};
return tool;
}
/**
* Add middleware to the pipeline
*/
use(middleware: ToolMiddleware): this {
this.middleware.push(middleware);
return this;
}
/**
* Set hooks
*/
setHooks(hooks: ToolHooks): this {
this.hooks = { ...this.hooks, ...hooks };
return this;
}
/**
* Check if a tool's optional dependency is installed
*/
async checkInstalled(id: string): Promise<boolean> {
// Check cache first
if (this.installCache.has(id)) {
return this.installCache.get(id)!;
}
const registered = this.tools.get(id);
if (!registered) {
return false;
}
const { packageName } = registered.metadata;
try {
// Try to resolve the package
await import(packageName);
this.installCache.set(id, true);
return true;
} catch {
this.installCache.set(id, false);
return false;
}
}
/**
* Check environment variables for a tool
*/
checkEnvVars(id: string): string[] {
const registered = this.tools.get(id);
if (!registered) {
return [];
}
const missing: string[] = [];
for (const envVar of registered.metadata.requiredEnv) {
if (!process.env[envVar]) {
missing.push(envVar);
}
}
return missing;
}
/**
* Get installation status for a tool
*/
async getInstallStatus(id: string): Promise<ToolInstallStatus | null> {
const registered = this.tools.get(id);
if (!registered) {
return null;
}
const installed = await this.checkInstalled(id);
const missingEnvVars = this.checkEnvVars(id);
return {
id,
installed,
missingEnvVars,
installCommand: installed ? undefined : registered.metadata.install.npm,
};
}
/**
* Get installation status for all tools
*/
async getAllInstallStatus(): Promise<ToolInstallStatus[]> {
const statuses: ToolInstallStatus[] = [];
for (const id of this.tools.keys()) {
const status = await this.getInstallStatus(id);
if (status) {
statuses.push(status);
}
}
return statuses;
}
/**
* Clear the registry
*/
clear(): void {
this.tools.clear();
this.middleware = [];
this.hooks = {};
this.installCache.clear();
}
/**
* Get the number of registered tools
*/
get size(): number {
return this.tools.size;
}
}
// Global registry instance
let globalRegistry: ToolsRegistry | null = null;
/**
* Get the global tools registry (singleton)
*/
export function getToolsRegistry(): ToolsRegistry {
if (!globalRegistry) {
globalRegistry = new ToolsRegistry();
}
return globalRegistry;
}
/**
* Create a new isolated registry (for testing or multi-agent scenarios)
*/
export function createToolsRegistry(): ToolsRegistry {
return new ToolsRegistry();
}
/**
* Reset the global registry (mainly for testing)
*/
export function resetToolsRegistry(): void {
if (globalRegistry) {
globalRegistry.clear();
}
globalRegistry = null;
}
@@ -0,0 +1,267 @@
/**
* AI SDK Tools Registry - Type Definitions
*
* Standard interfaces for tool registration, execution, and middleware.
*/
/**
* Tool execution context - passed to every tool call
*/
export interface ToolExecutionContext {
/** Unique identifier for the agent making the call */
agentId?: string;
/** Unique identifier for the current run/session */
runId?: string;
/** Trace ID for distributed tracing */
traceId?: string;
/** Abort signal for cancellation */
signal?: AbortSignal;
/** Logger instance */
logger?: ToolLogger;
/** Execution limits */
limits?: ToolLimits;
/** Redaction hooks for PII/sensitive data */
redaction?: RedactionHooks;
/** Additional metadata */
metadata?: Record<string, unknown>;
}
/**
* Tool execution limits
*/
export interface ToolLimits {
/** Maximum execution time in milliseconds */
timeoutMs?: number;
/** Maximum payload size in bytes */
maxPayloadBytes?: number;
/** Maximum number of results */
maxResults?: number;
/** Allowed domains (for search/scrape tools) */
allowedDomains?: string[];
/** Blocked domains (for search/scrape tools) */
blockedDomains?: string[];
}
/**
* Redaction hooks for sensitive data
*/
export interface RedactionHooks {
/** Redact input before tool execution */
redactInput?: (input: unknown) => unknown;
/** Redact output after tool execution */
redactOutput?: (output: unknown) => unknown;
/** Patterns to redact (regex strings) */
patterns?: string[];
}
/**
* Simple logger interface
*/
export interface ToolLogger {
debug: (message: string, ...args: unknown[]) => void;
info: (message: string, ...args: unknown[]) => void;
warn: (message: string, ...args: unknown[]) => void;
error: (message: string, ...args: unknown[]) => void;
}
/**
* Tool capability flags
*/
export interface ToolCapabilities {
search?: boolean;
extract?: boolean;
crawl?: boolean;
sandbox?: boolean;
browser?: boolean;
security?: boolean;
rag?: boolean;
code?: boolean;
}
/**
* Install hints for different package managers
*/
export interface InstallHints {
npm: string;
pnpm: string;
yarn: string;
bun: string;
}
/**
* Tool metadata for registry
*/
export interface ToolMetadata {
/** Unique tool identifier (kebab-case) */
id: string;
/** Display name for UI */
displayName: string;
/** Tool description */
description: string;
/** Categorization tags */
tags: string[];
/** Required environment variables */
requiredEnv: string[];
/** Optional environment variables */
optionalEnv: string[];
/** Install commands for package managers */
install: InstallHints;
/** Documentation slug (relative path in docs) */
docsSlug: string;
/** Tool capabilities */
capabilities: ToolCapabilities;
/** NPM package name */
packageName: string;
/** Version constraint (semver) */
versionConstraint?: string;
}
/**
* Tool execution result
*/
export interface ToolExecutionResult<T = unknown> {
/** Result data */
data: T;
/** Execution success */
success: boolean;
/** Error message if failed */
error?: string;
/** Execution duration in ms */
durationMs?: number;
/** Token/credit usage if applicable */
usage?: {
tokens?: number;
credits?: number;
};
/** Additional metadata */
metadata?: Record<string, unknown>;
}
/**
* Base interface for all PraisonAI tools
*/
export interface PraisonTool<TInput = unknown, TOutput = unknown> {
/** Tool name (used in function calling) */
name: string;
/** Tool description (used by LLM) */
description: string;
/** JSON Schema for parameters */
parameters: ToolParameterSchema;
/** Execute the tool */
execute: (input: TInput, context?: ToolExecutionContext) => Promise<TOutput>;
}
/**
* JSON Schema for tool parameters
*/
export interface ToolParameterSchema {
type: 'object';
properties: Record<string, ToolParameterProperty>;
required?: string[];
}
/**
* Individual parameter property schema
*/
export interface ToolParameterProperty {
type: string;
description?: string;
enum?: string[];
default?: unknown;
items?: ToolParameterProperty;
properties?: Record<string, ToolParameterProperty>;
}
/**
* Middleware function type
*/
export type ToolMiddleware = (
input: unknown,
context: ToolExecutionContext,
next: () => Promise<unknown>
) => Promise<unknown>;
/**
* Before/after hook types
*/
export interface ToolHooks {
beforeToolCall?: (toolName: string, input: unknown, context: ToolExecutionContext) => Promise<void> | void;
afterToolCall?: (toolName: string, input: unknown, output: unknown, context: ToolExecutionContext) => Promise<void> | void;
onError?: (toolName: string, error: Error, context: ToolExecutionContext) => Promise<void> | void;
}
/**
* Tool factory function type
*/
export type ToolFactory<TConfig = unknown, TInput = unknown, TOutput = unknown> = (
config?: TConfig
) => PraisonTool<TInput, TOutput>;
/**
* Registered tool entry in the registry
*/
export interface RegisteredTool {
metadata: ToolMetadata;
factory: ToolFactory;
/** Whether the optional dependency is installed */
isInstalled?: boolean;
}
/**
* Tool installation status
*/
export interface ToolInstallStatus {
id: string;
installed: boolean;
version?: string;
missingEnvVars: string[];
installCommand?: string;
}
/**
* Error thrown when optional dependency is missing
*/
export class MissingDependencyError extends Error {
constructor(
public readonly toolId: string,
public readonly packageName: string,
public readonly installHints: InstallHints,
public readonly requiredEnv: string[],
public readonly docsSlug: string
) {
const installCmd = installHints.npm;
const envVarsMsg = requiredEnv.length > 0
? `\n\nRequired environment variables:\n${requiredEnv.map(v => ` - ${v}`).join('\n')}`
: '';
super(
`Tool "${toolId}" requires package "${packageName}" which is not installed.\n\n` +
`To install:\n ${installCmd}\n` +
`Or with other package managers:\n` +
` pnpm: ${installHints.pnpm}\n` +
` yarn: ${installHints.yarn}\n` +
` bun: ${installHints.bun}` +
envVarsMsg +
`\n\nDocs: https://docs.praison.ai/js/${docsSlug}`
);
this.name = 'MissingDependencyError';
}
}
/**
* Error thrown when required environment variable is missing
*/
export class MissingEnvVarError extends Error {
constructor(
public readonly toolId: string,
public readonly envVar: string,
public readonly docsSlug: string
) {
super(
`Tool "${toolId}" requires environment variable "${envVar}" which is not set.\n\n` +
`Set it in your environment or .env file:\n ${envVar}=your_value_here\n\n` +
`Docs: https://docs.praison.ai/js/${docsSlug}`
);
this.name = 'MissingEnvVarError';
}
}
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff. Show more