blob: 4cb9f4c14c77426c9f58912ddd23bcf430500345 [file]
import 'dart:async';
import 'dart:convert';
import 'dart:js_interop';
import 'package:genkit/genkit.dart';
import 'package:jaspr/server.dart';
import 'package:schemantic/schemantic.dart';
import 'package:web/web.dart' as web;
import '../../models/agent_tool_event.dart';
import '../../models/agent_tool_id.dart';
export '../../models/agent_tool_metadata.dart';
/// Base class defining a structured tool that can be executed by the VibePad AI agent.
///
/// Implements input/output schema typing using [SchemanticType] and maps results
/// to standard [AgentToolEvent] telemetry.
abstract class AgentTool<I, O> {
const AgentTool();
/// The type-safe identifier of this tool.
AgentToolId get toolId;
/// The unique registration name of the tool (e.g. 'readFile').
String get name => toolId.name;
/// User-facing or model-facing explanation of what the tool does.
String get description;
/// Schema type definitions for validating the tool's input structure.
SchemanticType<I> get inputSchema;
/// Schema type definitions for validating the tool's output structure.
SchemanticType<O> get outputSchema;
/// Executes the core logic of the tool with the given strongly-typed input.
Future<O> run(I input);
String _formatLog(dynamic object) {
try {
final json = (object as dynamic).toJson();
return const JsonEncoder.withIndent(' ').convert(json);
} catch (_) {
try {
return const JsonEncoder.withIndent(' ').convert(object);
} catch (_) {
return object.toString();
}
}
}
void _logToConsole(String message, {bool isError = false}) {
if (identical(0, 0.0)) {
if (isError) {
web.console.error(message.toJS);
} else {
web.console.log(message.toJS);
}
} else {
print(message);
}
}
/// Converts this custom tool definition into a standard Genkit [Tool],
/// registering callbacks to track execution events for telemetry and UI visualization.
Tool<I, O> toGenkitTool({required void Function(AgentToolEvent) onToolEvent}) {
return Tool<I, O>(
name: name,
description: description,
inputSchema: inputSchema,
outputSchema: outputSchema,
fn: (input, _) async {
try {
onToolEvent(mapStartToToolEvent(input));
if (kDebugMode) {
_logToConsole('[VibePad Agent Tool] $name input:');
_logToConsole(_formatLog(input));
}
final output = await run(input);
onToolEvent(mapToToolEvent(input, output));
if (kDebugMode) {
_logToConsole('[VibePad Agent Tool] $name output:');
_logToConsole(_formatLog(output));
}
return output;
} catch (error) {
if (kDebugMode) {
_logToConsole('[VibePad Agent Tool] $name failed: $error', isError: true);
}
final errEvent = createToolEvent(
status: ToolStatus.error,
summary: 'Tool execution failed',
errorMessage: error.toString(),
);
onToolEvent(errEvent);
rethrow;
}
},
);
}
/// Extracts the target file path from the tool's input, if applicable.
///
/// Override this in file-based tools to avoid overriding [mapStartToToolEvent]
/// just to set `targetPath`.
String? targetPathForInput(I input) => null;
/// Maps the tool's input to a starting [AgentToolEvent] for logging.
AgentToolEvent mapStartToToolEvent(I input) {
return createToolEvent(
status: ToolStatus.running,
summary: name,
targetPath: targetPathForInput(input),
);
}
/// Helper to create a standardized [AgentToolEvent] pre-populated with this tool's
/// identity.
AgentToolEvent createToolEvent({
required ToolStatus status,
required String summary,
bool skipped = false,
String? subAction,
String? targetPath,
int? startLine,
int? endLine,
WriteFileChangeKind? writeFileChangeKind,
bool requiresPreviewRebuild = false,
int? patchReplacementCount,
String? errorMessage,
String? callId,
String? screenshotBase64,
}) {
final requestPart = Zone.current[ToolRequestPart] as ToolRequestPart?;
final resolvedCallId = callId ?? requestPart?.toolRequest.ref;
return AgentToolEvent(
toolId: toolId,
status: status,
summary: summary,
skipped: skipped,
subAction: subAction,
targetPath: targetPath,
startLine: startLine,
endLine: endLine,
writeFileChangeKind: writeFileChangeKind,
requiresPreviewRebuild: requiresPreviewRebuild,
patchReplacementCount: patchReplacementCount,
errorMessage: errorMessage,
callId: resolvedCallId,
screenshotBase64: screenshotBase64,
);
}
/// Maps the tool's input and execution results to an [AgentToolEvent] for logging.
AgentToolEvent mapToToolEvent(I input, O output);
}