automagik-cli
Version:
Automagik CLI - A powerful command-line interface for interacting with Automagik Hive multi-agent AI systems
808 lines (807 loc) ⢠42.3 kB
JavaScript
import { request as gaxiosRequest } from 'gaxios';
import { appConfig } from './settings.js';
export class LocalAPIClient {
baseUrl;
timeout;
retryAttempts;
constructor() {
this.baseUrl = appConfig.apiBaseUrl;
this.timeout = appConfig.apiTimeout;
this.retryAttempts = appConfig.apiRetryAttempts;
}
getDefaultHeaders() {
const headers = {
'accept': 'application/json',
};
if (appConfig.apiKey) {
headers['x-api-key'] = appConfig.apiKey;
}
return headers;
}
// Update base URL for server configuration
setBaseUrl(url) {
this.baseUrl = url;
}
// Note: API key is read from appConfig, not stored locally
// This method exists for compatibility but doesn't store the key
setApiKey(apiKey) {
// API key is handled through appConfig/settings, not stored here
// This method is for compatibility with existing code
}
async makeRequest(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
try {
const response = await gaxiosRequest({
url,
timeout: this.timeout,
headers: {
...this.getDefaultHeaders(),
...(options.headers || {}),
},
...options,
});
return {
data: response.data,
session_id: response.headers.get('x-session-id') || response.data?.session_id || undefined,
};
}
catch (error) {
if (appConfig.cliDebug) {
console.error('API Request failed:', error);
}
return {
data: null,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
// Fetch OpenAPI schema
async getSchema() {
return this.makeRequest('/openapi.json', {
method: 'GET',
});
}
// List available agents
async listAgents() {
return this.makeRequest('/playground/agents', {
method: 'GET',
});
}
// List available teams
async listTeams() {
return this.makeRequest('/playground/teams', {
method: 'GET',
});
}
// List available workflows
async listWorkflows() {
return this.makeRequest('/playground/workflows', {
method: 'GET',
});
}
// Invoke an agent (non-streaming)
async invokeAgent(request) {
const formData = new FormData();
formData.append('message', request.message);
formData.append('stream', 'false');
if (request.session_id) {
formData.append('session_id', request.session_id);
}
if (request.user_id) {
formData.append('user_id', request.user_id);
}
if (request.user_name) {
formData.append('user_name', request.user_name);
}
if (request.phone_number) {
formData.append('phone_number', request.phone_number);
}
if (request.cpf) {
formData.append('cpf', request.cpf);
}
return this.makeRequest(`/playground/agents/${request.agent_id}/runs`, {
method: 'POST',
data: formData,
headers: {
// Don't set Content-Type, let browser set it with boundary for multipart
},
});
}
// Invoke a team (non-streaming)
async invokeTeam(request) {
const formData = new FormData();
formData.append('message', request.message);
formData.append('stream', 'false');
if (request.session_id) {
formData.append('session_id', request.session_id);
}
if (request.user_id) {
formData.append('user_id', request.user_id);
}
if (request.user_name) {
formData.append('user_name', request.user_name);
}
if (request.phone_number) {
formData.append('phone_number', request.phone_number);
}
if (request.cpf) {
formData.append('cpf', request.cpf);
}
return this.makeRequest(`/playground/teams/${request.team_id}/runs`, {
method: 'POST',
data: formData,
headers: {
// Don't set Content-Type, let browser set it with boundary for multipart
},
});
}
// Execute a workflow (non-streaming)
async executeWorkflow(request) {
return this.makeRequest(`/playground/workflows/${request.workflow_id}/runs`, {
method: 'POST',
data: {
params: request.params,
session_id: request.session_id,
user_id: request.user_id,
user_name: request.user_name,
phone_number: request.phone_number,
cpf: request.cpf,
},
});
}
// Stream agent response with real API streaming
async streamAgent(request, onMessage, onError, onComplete, abortSignal) {
try {
const formData = new FormData();
formData.append('message', request.message);
formData.append('stream', 'true');
formData.append('monitor', 'true');
if (request.session_id) {
formData.append('session_id', request.session_id);
}
if (request.user_id) {
formData.append('user_id', request.user_id);
}
if (request.user_name) {
formData.append('user_name', request.user_name);
}
if (request.phone_number) {
formData.append('phone_number', request.phone_number);
}
if (request.cpf) {
formData.append('cpf', request.cpf);
}
const url = `${this.baseUrl}/playground/agents/${request.agent_id}/runs`;
const response = await gaxiosRequest({
url,
method: 'POST',
data: formData,
headers: this.getDefaultHeaders(),
responseType: 'stream',
timeout: 0, // No timeout for streaming requests
signal: abortSignal,
});
let buffer = '';
let finalContent = '';
let sessionId = request.session_id;
response.data.on('data', (chunk) => {
const chunkStr = chunk.toString();
if (appConfig.cliDebug) {
console.log('[STREAM CHUNK]:', chunkStr.slice(0, 200) + (chunkStr.length > 200 ? '...' : ''));
}
buffer += chunkStr;
// Process complete JSON objects in buffer
// JSON objects are separated by }{ not newlines
let separatorIndex;
while ((separatorIndex = buffer.indexOf('}{')) !== -1) {
const jsonStr = buffer.slice(0, separatorIndex + 1).trim();
buffer = '{' + buffer.slice(separatorIndex + 2); // Keep the opening { for next object
if (jsonStr) {
try {
const event = JSON.parse(jsonStr);
if (appConfig.cliDebug) {
console.log('[EVENT]:', event.event, event.content ? `"${event.content.slice(0, 50)}..."` : '');
console.log('[EVENT DATA]:', JSON.stringify(event, null, 2));
}
// Extract session_id from first event
if (event.session_id && !sessionId) {
sessionId = event.session_id;
}
// Handle content events (including thinking)
if (event.event === 'TeamRunResponseContent' || event.event === 'RunResponseContent') {
if (event.content) {
// Handle both regular content and thinking content
const content = event.content;
if (event.content_type === 'str') {
finalContent += content;
}
onMessage({
content: content,
done: false,
session_id: sessionId,
metadata: {
type: 'content',
event: event.event,
content_type: event.content_type,
},
});
}
}
// Handle team start events
if (event.event === 'TeamRunStarted') {
const teamName = event.team_name || event.team_id || event.team?.team_name || event.team?.team_id || 'unknown';
onMessage({
content: `šµ ${teamName}`,
done: false,
session_id: sessionId,
metadata: {
type: 'team_start',
event: event.event,
team: {
team_id: event.team_id,
team_name: event.team_name,
run_id: event.run_id,
model: event.model,
model_provider: event.model_provider,
},
},
});
}
// Handle tool call events
if (event.event === 'TeamToolCallStarted' || event.event === 'ToolCallStarted') {
const toolName = event.tool?.tool_name || event.tool_name || 'unknown';
const toolArgs = event.tool?.tool_args || event.tool_args || {};
const argsStr = Object.keys(toolArgs).length > 0 ? JSON.stringify(toolArgs, null, 2) : '';
onMessage({
content: `š§ ${toolName}${argsStr ? '\n\nArguments:\n' + argsStr : ''}`,
done: false,
session_id: sessionId,
metadata: {
type: 'tool_start',
event: event.event,
tool: {
tool_call_id: event.tool?.tool_call_id,
tool_name: event.tool?.tool_name || event.tool_name,
tool_args: event.tool?.tool_args || event.tool_args,
created_at: event.tool?.created_at || event.created_at,
agent_id: event.agent_id,
agent_name: event.agent_name,
run_id: event.run_id,
},
},
});
}
if (event.event === 'ToolCallCompleted' || event.event === 'TeamToolCallCompleted') {
const toolName = event.tool?.tool_name || event.tool_name || 'unknown';
const duration = event.tool?.metrics?.time ? ` (${(event.tool.metrics.time * 1000).toFixed(0)}ms)` : '';
const toolResult = event.tool?.result || event.tool?.tool_result || event.tool_result || event.result;
const resultStr = toolResult && typeof toolResult === 'string' && toolResult.trim() ?
(toolResult.length > 500 ? toolResult.substring(0, 500) + '...' : toolResult) : '';
onMessage({
content: `ā
${toolName}${duration}${resultStr ? '\n\nResult:\n' + resultStr : ''}`,
done: false,
session_id: sessionId,
metadata: {
type: 'tool_complete',
event: event.event,
tool: {
tool_call_id: event.tool?.tool_call_id,
tool_name: event.tool?.tool_name || event.tool_name,
tool_args: event.tool?.tool_args || event.tool_args,
tool_result: event.tool?.result || event.tool?.tool_result || event.tool_result || event.result,
metrics: event.tool?.metrics || event.metrics,
created_at: event.tool?.created_at || event.created_at,
agent_id: event.agent_id,
agent_name: event.agent_name,
run_id: event.run_id,
tool_call_error: event.tool?.tool_call_error,
},
},
});
}
// Handle agent start events
if (event.event === 'RunStarted') {
const agentName = event.agent_name || event.agent?.agent_name || event.agent_id || event.agent?.agent_id || 'unknown';
onMessage({
content: `š¤ ${agentName}`,
done: false,
session_id: sessionId,
metadata: {
type: 'agent_start',
event: event.event,
agent: {
agent_id: event.agent_id,
agent_name: event.agent_name,
run_id: event.run_id,
session_id: event.session_id,
team_session_id: event.team_session_id,
model: event.model,
model_provider: event.model_provider,
},
},
});
}
// Handle memory events
if (event.event === 'MemoryUpdateStarted' || event.event === 'MemoryUpdateCompleted') {
const memoryType = event.memory_type || event.memory?.type || event.type || 'user memory';
const action = event.event === 'MemoryUpdateStarted' ? 'Updating' : 'Updated';
const memoryContent = event.memory_content || event.content || event.memory?.content || '';
// Debug logging for memory events
if (appConfig.cliDebug) {
console.log('[MEMORY EVENT DEBUG]:', {
event: event.event,
memory_type: event.memory_type,
memory_content: event.memory_content,
content: event.content,
memory: event.memory,
type: event.type,
extractedType: memoryType,
extractedContent: memoryContent,
});
}
onMessage({
content: `š§ ${action} ${memoryType}${memoryContent ? '\n\nContent:\n' + memoryContent : ''}`,
done: false,
session_id: sessionId,
metadata: {
type: 'memory_update',
event: event.event,
eventId: `${event.event}-${Date.now()}-${Math.random()}`, // Unique event ID
memory: event.memory || {
type: event.memory_type || event.type,
content: event.memory_content || event.content,
metadata: event.metadata,
},
},
});
}
// Handle thinking events
if (event.event === 'ThinkingStarted' || event.event === 'ThinkingCompleted') {
const thinkingAction = event.event === 'ThinkingStarted' ? 'Thinking...' : 'Thought complete';
onMessage({
content: `š¤ ${thinkingAction}`,
done: false,
session_id: sessionId,
metadata: {
type: 'thinking',
event: event.event,
thinking: {
content: event.content || '',
reasoning: event.reasoning || '',
},
},
});
}
// Handle RAG events
if (event.event === 'RAGQueryStarted' || event.event === 'RAGQueryCompleted') {
const ragAction = event.event === 'RAGQueryStarted' ? 'Searching' : 'Found';
const query = event.query || 'knowledge base';
onMessage({
content: `š ${ragAction} in ${query}`,
done: false,
session_id: sessionId,
metadata: {
type: 'rag_query',
event: event.event,
rag: {
query: event.query,
results: event.results,
metadata: event.metadata,
},
},
});
}
// Handle completion events
if (event.event === 'TeamRunCompleted' || event.event === 'RunCompleted') {
onMessage({
content: '',
done: true,
session_id: sessionId,
});
onComplete();
return;
}
}
catch (parseError) {
console.warn('Failed to parse JSON:', jsonStr?.slice(0, 100));
}
}
}
});
response.data.on('end', () => {
// Process any remaining JSON in buffer
if (buffer.trim()) {
try {
const event = JSON.parse(buffer.trim());
if (appConfig.cliDebug) {
console.log('[FINAL EVENT]:', event.event);
}
// Handle final content events
if (event.event === 'TeamRunResponseContent' || event.event === 'RunResponseContent') {
if (event.content) {
const content = event.content;
if (event.content_type === 'str') {
finalContent += content;
}
onMessage({
content: content,
done: false,
session_id: sessionId,
});
}
}
// Handle completion events
if (event.event === 'TeamRunCompleted' || event.event === 'RunCompleted') {
onMessage({
content: '',
done: true,
session_id: sessionId,
});
onComplete();
return;
}
}
catch (parseError) {
console.warn('Failed to parse final JSON:', buffer.slice(0, 100));
}
}
// Complete anyway
onMessage({
content: '',
done: true,
session_id: sessionId,
});
onComplete();
});
response.data.on('error', (error) => {
if (appConfig.cliDebug) {
console.log('[STREAM ERROR]:', error.message);
}
// Don't treat timeout/abort as fatal error if we got some content
if (error.message.includes('aborted') || error.message.includes('timeout')) {
onMessage({
content: '\nā ļø Stream interrupted - response may be incomplete',
done: true,
session_id: sessionId,
});
onComplete();
}
else {
onError(error);
}
});
}
catch (error) {
onError(error instanceof Error ? error : new Error('Unknown streaming error'));
}
}
// Stream team response with real API streaming
async streamTeam(request, onMessage, onError, onComplete, abortSignal) {
try {
const formData = new FormData();
formData.append('message', request.message);
formData.append('stream', 'true');
formData.append('monitor', 'true');
if (request.session_id) {
formData.append('session_id', request.session_id);
}
if (request.user_id) {
formData.append('user_id', request.user_id);
}
if (request.user_name) {
formData.append('user_name', request.user_name);
}
if (request.phone_number) {
formData.append('phone_number', request.phone_number);
}
if (request.cpf) {
formData.append('cpf', request.cpf);
}
const url = `${this.baseUrl}/playground/teams/${request.team_id}/runs`;
const response = await gaxiosRequest({
url,
method: 'POST',
data: formData,
headers: this.getDefaultHeaders(),
responseType: 'stream',
timeout: 0, // No timeout for streaming requests
signal: abortSignal,
});
let buffer = '';
let finalContent = '';
let sessionId = request.session_id;
response.data.on('data', (chunk) => {
buffer += chunk.toString();
// Process complete JSON objects in buffer
// JSON objects are separated by }{ not newlines
let separatorIndex;
while ((separatorIndex = buffer.indexOf('}{')) !== -1) {
const jsonStr = buffer.slice(0, separatorIndex + 1).trim();
buffer = '{' + buffer.slice(separatorIndex + 2); // Keep the opening { for next object
if (jsonStr) {
try {
const event = JSON.parse(jsonStr);
// Extract session_id from first event
if (event.session_id && !sessionId) {
sessionId = event.session_id;
}
// Handle content events (including thinking)
if (event.event === 'TeamRunResponseContent' || event.event === 'RunResponseContent') {
if (event.content) {
// Handle both regular content and thinking content
const content = event.content;
if (event.content_type === 'str') {
finalContent += content;
}
onMessage({
content: content,
done: false,
session_id: sessionId,
metadata: {
type: 'content',
event: event.event,
content_type: event.content_type,
},
});
}
}
// Handle team start events
if (event.event === 'TeamRunStarted') {
const teamName = event.team_name || event.team_id || event.team?.team_name || event.team?.team_id || 'unknown';
onMessage({
content: `šµ ${teamName}`,
done: false,
session_id: sessionId,
metadata: {
type: 'team_start',
event: event.event,
team: {
team_id: event.team_id,
team_name: event.team_name,
run_id: event.run_id,
model: event.model,
model_provider: event.model_provider,
},
},
});
}
// Handle tool call events
if (event.event === 'TeamToolCallStarted' || event.event === 'ToolCallStarted') {
const toolName = event.tool?.tool_name || event.tool_name || 'unknown';
const toolArgs = event.tool?.tool_args || event.tool_args || {};
const argsStr = Object.keys(toolArgs).length > 0 ? JSON.stringify(toolArgs, null, 2) : '';
onMessage({
content: `š§ ${toolName}${argsStr ? '\n\nArguments:\n' + argsStr : ''}`,
done: false,
session_id: sessionId,
metadata: {
type: 'tool_start',
event: event.event,
tool: {
tool_call_id: event.tool?.tool_call_id,
tool_name: event.tool?.tool_name || event.tool_name,
tool_args: event.tool?.tool_args || event.tool_args,
created_at: event.tool?.created_at || event.created_at,
agent_id: event.agent_id,
agent_name: event.agent_name,
run_id: event.run_id,
},
},
});
}
if (event.event === 'ToolCallCompleted' || event.event === 'TeamToolCallCompleted') {
const toolName = event.tool?.tool_name || event.tool_name || 'unknown';
const duration = event.tool?.metrics?.time ? ` (${(event.tool.metrics.time * 1000).toFixed(0)}ms)` : '';
const toolResult = event.tool?.result || event.tool?.tool_result || event.tool_result || event.result;
const resultStr = toolResult && typeof toolResult === 'string' && toolResult.trim() ?
(toolResult.length > 500 ? toolResult.substring(0, 500) + '...' : toolResult) : '';
onMessage({
content: `ā
${toolName}${duration}${resultStr ? '\n\nResult:\n' + resultStr : ''}`,
done: false,
session_id: sessionId,
metadata: {
type: 'tool_complete',
event: event.event,
tool: {
tool_call_id: event.tool?.tool_call_id,
tool_name: event.tool?.tool_name || event.tool_name,
tool_args: event.tool?.tool_args || event.tool_args,
tool_result: event.tool?.result || event.tool?.tool_result || event.tool_result || event.result,
metrics: event.tool?.metrics || event.metrics,
created_at: event.tool?.created_at || event.created_at,
agent_id: event.agent_id,
agent_name: event.agent_name,
run_id: event.run_id,
tool_call_error: event.tool?.tool_call_error,
},
},
});
}
// Handle agent start events
if (event.event === 'RunStarted') {
const agentName = event.agent_name || event.agent?.agent_name || event.agent_id || event.agent?.agent_id || 'unknown';
onMessage({
content: `š¤ ${agentName}`,
done: false,
session_id: sessionId,
metadata: {
type: 'agent_start',
event: event.event,
agent: {
agent_id: event.agent_id,
agent_name: event.agent_name,
run_id: event.run_id,
session_id: event.session_id,
team_session_id: event.team_session_id,
model: event.model,
model_provider: event.model_provider,
},
},
});
}
// Handle memory events
if (event.event === 'MemoryUpdateStarted' || event.event === 'MemoryUpdateCompleted') {
const memoryType = event.memory_type || event.memory?.type || event.type || 'user memory';
const action = event.event === 'MemoryUpdateStarted' ? 'Updating' : 'Updated';
const memoryContent = event.memory_content || event.content || event.memory?.content || '';
// Debug logging for memory events
if (appConfig.cliDebug) {
console.log('[MEMORY EVENT DEBUG]:', {
event: event.event,
memory_type: event.memory_type,
memory_content: event.memory_content,
content: event.content,
memory: event.memory,
type: event.type,
extractedType: memoryType,
extractedContent: memoryContent,
});
}
onMessage({
content: `š§ ${action} ${memoryType}${memoryContent ? '\n\nContent:\n' + memoryContent : ''}`,
done: false,
session_id: sessionId,
metadata: {
type: 'memory_update',
event: event.event,
eventId: `${event.event}-${Date.now()}-${Math.random()}`, // Unique event ID
memory: event.memory || {
type: event.memory_type || event.type,
content: event.memory_content || event.content,
metadata: event.metadata,
},
},
});
}
// Handle thinking events
if (event.event === 'ThinkingStarted' || event.event === 'ThinkingCompleted') {
const thinkingAction = event.event === 'ThinkingStarted' ? 'Thinking...' : 'Thought complete';
onMessage({
content: `š¤ ${thinkingAction}`,
done: false,
session_id: sessionId,
metadata: {
type: 'thinking',
event: event.event,
thinking: {
content: event.content || '',
reasoning: event.reasoning || '',
},
},
});
}
// Handle RAG events
if (event.event === 'RAGQueryStarted' || event.event === 'RAGQueryCompleted') {
const ragAction = event.event === 'RAGQueryStarted' ? 'Searching' : 'Found';
const query = event.query || 'knowledge base';
onMessage({
content: `š ${ragAction} in ${query}`,
done: false,
session_id: sessionId,
metadata: {
type: 'rag_query',
event: event.event,
rag: {
query: event.query,
results: event.results,
metadata: event.metadata,
},
},
});
}
// Handle completion events
if (event.event === 'TeamRunCompleted') {
onMessage({
content: '',
done: true,
session_id: sessionId,
});
onComplete();
return;
}
}
catch (parseError) {
console.warn('Failed to parse JSON:', jsonStr?.slice(0, 100));
}
}
}
});
response.data.on('end', () => {
// Process any remaining JSON in buffer
if (buffer.trim()) {
try {
const event = JSON.parse(buffer.trim());
if (appConfig.cliDebug) {
console.log('[FINAL EVENT]:', event.event);
}
// Handle final content events
if (event.event === 'TeamRunResponseContent' || event.event === 'RunResponseContent') {
if (event.content) {
const content = event.content;
if (event.content_type === 'str') {
finalContent += content;
}
onMessage({
content: content,
done: false,
session_id: sessionId,
});
}
}
// Handle completion events
if (event.event === 'TeamRunCompleted' || event.event === 'RunCompleted') {
onMessage({
content: '',
done: true,
session_id: sessionId,
});
onComplete();
return;
}
}
catch (parseError) {
console.warn('Failed to parse final JSON:', buffer.slice(0, 100));
}
}
// Complete anyway
onMessage({
content: '',
done: true,
session_id: sessionId,
});
onComplete();
});
response.data.on('error', (error) => {
if (appConfig.cliDebug) {
console.log('[STREAM ERROR]:', error.message);
}
// Don't treat timeout/abort as fatal error if we got some content
if (error.message.includes('aborted') || error.message.includes('timeout')) {
onMessage({
content: '\nā ļø Stream interrupted - response may be incomplete',
done: true,
session_id: sessionId,
});
onComplete();
}
else {
onError(error);
}
});
}
catch (error) {
onError(error instanceof Error ? error : new Error('Unknown streaming error'));
}
}
// Health check
async healthCheck() {
return this.makeRequest('/api/v1/health', {
method: 'GET',
});
}
// Generic API call method for custom endpoints
async apiCall(endpoint, options = {}) {
return this.makeRequest(endpoint, options);
}
}
export const localAPIClient = new LocalAPIClient();