210 lines
5.1 KiB
Dart
210 lines
5.1 KiB
Dart
// Agent execution engine
|
|
// Spawns and manages agent instances
|
|
|
|
import 'dart:async';
|
|
|
|
import '../api/openrouter_client.dart';
|
|
import '../chat/tool_loop_service.dart';
|
|
import '../local_state.dart';
|
|
import 'agent_context.dart';
|
|
|
|
/// Executes a single agent
|
|
class AgentExecutor {
|
|
static final AgentExecutor _instance = AgentExecutor._internal();
|
|
factory AgentExecutor() => _instance;
|
|
AgentExecutor._internal();
|
|
|
|
final Map<String, _RunningAgent> _agents = {};
|
|
int _idCounter = 1;
|
|
|
|
/// Spawn and run an agent
|
|
Future<String> spawnAgent({
|
|
required AgentDefinition definition,
|
|
required AgentContext context,
|
|
required String task,
|
|
required String apiKey,
|
|
required String model,
|
|
}) async {
|
|
final agentId = 'agent_${_idCounter++}';
|
|
|
|
final agent = _RunningAgent(
|
|
id: agentId,
|
|
definition: definition,
|
|
context: context.createChildContext(childAgentId: agentId),
|
|
task: task,
|
|
apiKey: apiKey,
|
|
model: model,
|
|
);
|
|
|
|
_agents[agentId] = agent;
|
|
|
|
// Start agent in background
|
|
agent.run().then((result) {
|
|
// Agent completed
|
|
context.recordSubAgentResult(agentId, result);
|
|
}).catchError((e) {
|
|
print('Agent $agentId failed: $e');
|
|
context.recordSubAgentResult(
|
|
agentId,
|
|
AgentResult.failure(agentId: agentId, error: e.toString()),
|
|
);
|
|
});
|
|
|
|
return agentId;
|
|
}
|
|
|
|
/// Get agent result (non-blocking)
|
|
AgentResult? getResult(String agentId) {
|
|
final agent = _agents[agentId];
|
|
if (agent == null) {
|
|
return null;
|
|
}
|
|
|
|
return agent.result;
|
|
}
|
|
|
|
/// Wait for agent completion
|
|
Future<AgentResult> waitForAgent(String agentId,
|
|
{Duration timeout = const Duration(hours: 24)}) async {
|
|
final agent = _agents[agentId];
|
|
if (agent == null) {
|
|
throw Exception('Agent $agentId not found');
|
|
}
|
|
|
|
return agent.waitForCompletion(timeout: timeout);
|
|
}
|
|
|
|
/// Get all running agents
|
|
List<Map<String, dynamic>> getAllAgents() {
|
|
return _agents.entries.map((e) {
|
|
return {
|
|
'id': e.key,
|
|
'type': e.value.definition.type,
|
|
'task': e.value.task,
|
|
'status': e.value.isComplete ? 'completed' : 'running',
|
|
'result': e.value.result?.toJson(),
|
|
};
|
|
}).toList();
|
|
}
|
|
|
|
/// Cancel an agent
|
|
Future<bool> cancelAgent(String agentId) async {
|
|
final agent = _agents[agentId];
|
|
if (agent == null) {
|
|
return false;
|
|
}
|
|
|
|
agent.cancel();
|
|
_agents.remove(agentId);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// Represents a running agent instance
|
|
class _RunningAgent {
|
|
final String id;
|
|
final AgentDefinition definition;
|
|
final AgentContext context;
|
|
final String task;
|
|
final String apiKey;
|
|
final String model;
|
|
|
|
DateTime _startTime = DateTime.now();
|
|
DateTime? _endTime;
|
|
AgentResult? result;
|
|
bool _cancelled = false;
|
|
|
|
final Completer<AgentResult> _completer = Completer();
|
|
|
|
_RunningAgent({
|
|
required this.id,
|
|
required this.definition,
|
|
required this.context,
|
|
required this.task,
|
|
required this.apiKey,
|
|
required this.model,
|
|
});
|
|
|
|
bool get isComplete => _endTime != null;
|
|
|
|
/// Run the agent
|
|
Future<AgentResult> run() async {
|
|
try {
|
|
// Create API client for this agent
|
|
final client = OpenRouterClient(
|
|
config: OpenRouterConfig(
|
|
apiKey: apiKey,
|
|
model: model,
|
|
),
|
|
);
|
|
|
|
// Create tool loop for this agent
|
|
final toolLoop = ToolLoopService();
|
|
|
|
// Build agent-specific system prompt
|
|
final systemPrompt = '''${definition.getSystemPrompt()}
|
|
|
|
Task: $task
|
|
|
|
Context:
|
|
- Session ID: ${context.sessionId}
|
|
- Working Directory: ${context.workingDirectory}
|
|
- Parent Agent: ${context.parentAgentId}
|
|
${context.sharedState.isNotEmpty ? '- Shared State: ${context.sharedState}' : ''}
|
|
|
|
Use available tools to complete this task. Report your findings clearly.''';
|
|
|
|
// Run the tool loop for this agent
|
|
final toolResult = await toolLoop.runTurn(
|
|
client: client,
|
|
model: model,
|
|
apiKey: apiKey,
|
|
getSettings: () => context.settings,
|
|
apiMessages: context.conversationHistory,
|
|
userText: task,
|
|
workingDirectory: context.workingDirectory,
|
|
);
|
|
|
|
// Extract output
|
|
final output = toolResult.responseText;
|
|
|
|
// Create result
|
|
result = AgentResult.success(
|
|
agentId: id,
|
|
output: output,
|
|
data: {
|
|
'messages_exchanged': toolResult.apiMessages.length,
|
|
'web_searches': toolResult.webSearchRequests,
|
|
'web_fetches': toolResult.webFetchRequests,
|
|
},
|
|
duration: DateTime.now().difference(_startTime),
|
|
);
|
|
|
|
_endTime = DateTime.now();
|
|
_completer.complete(result!);
|
|
|
|
return result!;
|
|
} catch (e, st) {
|
|
result = AgentResult.failure(
|
|
agentId: id,
|
|
error: e.toString(),
|
|
duration: DateTime.now().difference(_startTime),
|
|
);
|
|
|
|
_endTime = DateTime.now();
|
|
_completer.completeError(e, st);
|
|
|
|
return result!;
|
|
}
|
|
}
|
|
|
|
/// Wait for agent to complete
|
|
Future<AgentResult> waitForCompletion({Duration timeout = const Duration(hours: 24)}) {
|
|
return _completer.future.timeout(timeout);
|
|
}
|
|
|
|
/// Cancel this agent
|
|
void cancel() {
|
|
_cancelled = true;
|
|
}
|
|
}
|