blob: 62eb046a746137fbb947c93a5190aed2fa783214 [file]
import 'dart:convert';
import 'package:schemantic/schemantic.dart';
import '../../../shared/logic/app_event_bus.dart';
import '../../models/agent_tool_event.dart';
import '../../models/agent_tool_id.dart';
import '../base/agent_tool.dart';
import '../tool_schemas.dart';
/// Simulates entering text into an input widget (TextField, etc.) by its nodeId.
class EnterTextTool extends AgentTool<EnterTextInput, EnterTextOutput> {
const EnterTextTool({
required this.commandBus,
});
final AppCommandBus commandBus;
@override
AgentToolId get toolId => AgentToolId.enterText;
@override
String get description =>
'Enter text into a text input widget (like TextField) in the running app preview using its node ID. '
'The widget must be or contain an editable text field.';
@override
SchemanticType<EnterTextInput> get inputSchema => EnterTextInput.$schema;
@override
SchemanticType<EnterTextOutput> get outputSchema => EnterTextOutput.$schema;
Future<String?> _invokeExtension(String method, Map<String, String> args) {
return commandBus.dispatchAsync(InvokePreviewExtensionCommand(
method: method,
args: args,
));
}
@override
Future<EnterTextOutput> run(EnterTextInput input) async {
final state = await commandBus.dispatchAsync(RequestPreviewStateCommand());
if (!state.hasActiveRuntime) {
return EnterTextOutput(
success: false,
error: 'No app preview is running. Use controlApp to start the preview first.',
);
}
try {
final responseStr = await _invokeExtension(
'ext.vibepad.enterText',
{
'id': input.nodeId,
'text': input.text,
},
);
if (responseStr == null || responseStr.isEmpty) {
return EnterTextOutput(
success: false,
error: 'Could not enter text. No response from the running app.',
);
}
final Map<String, dynamic> decoded = jsonDecode(responseStr) as Map<String, dynamic>;
final success = decoded['success'] as bool? ?? false;
if (!success) {
return EnterTextOutput(
success: false,
error: decoded['error'] as String? ?? 'Failed to enter text.',
);
}
return EnterTextOutput(
success: true,
);
} catch (e) {
return EnterTextOutput(
success: false,
error: 'Failed to enter text into widget "${input.nodeId}": $e',
);
}
}
String _truncate(String val, int maxLength) {
if (val.length <= maxLength) {
return val;
}
return '${val.substring(0, maxLength)}...';
}
@override
AgentToolEvent mapStartToToolEvent(EnterTextInput input) {
final txt = _truncate(input.text, 20);
return createToolEvent(
status: ToolStatus.running,
summary: 'Entering "$txt" into widget "${input.nodeId}"',
);
}
@override
AgentToolEvent mapToToolEvent(
EnterTextInput input,
EnterTextOutput output,
) {
final txt = _truncate(input.text, 20);
return createToolEvent(
status: output.success ? ToolStatus.success : ToolStatus.error,
summary: output.success
? 'Entered "$txt" into widget "${input.nodeId}"'
: 'Failed to enter text into widget "${input.nodeId}"',
errorMessage: output.error,
);
}
}