Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions chat_core/lib/chat_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export 'src/models/tool_call.dart';
export 'src/models/usage.dart';
export 'src/service/chat_service.dart';
export 'src/service/portkey_chat_service.dart';
export 'src/tool/tool.dart';
export 'src/tool/tool_registry.dart';
6 changes: 6 additions & 0 deletions chat_core/lib/src/tool/tool.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
abstract class Tool {
String get name;
String get description;
Map<String, dynamic> get parameters;
Future<String> execute(Map<String, dynamic> arguments);
}
32 changes: 32 additions & 0 deletions chat_core/lib/src/tool/tool_registry.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'tool.dart';

class ToolRegistry {
final _tools = <String, Tool>{};

void register(Tool tool) {
_tools[tool.name] = tool;
}

void unregister(String toolName) {
_tools.remove(toolName);
}

Tool? getTool(String name) => _tools[name];

List<Tool> get enabledTools => List.unmodifiable(_tools.values);

List<Map<String, dynamic>> toToolDefinitions() {
return enabledTools
.map(
(tool) => {
'type': 'function',
'function': {
'name': tool.name,
'description': tool.description,
'parameters': tool.parameters,
},
},
)
.toList();
}
}
114 changes: 114 additions & 0 deletions chat_core/test/tool/tool_registry_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import 'package:chat_core/chat_core.dart';
import 'package:test/test.dart';

class FakeTool implements Tool {
FakeTool({
required this.name,
this.description = '',
Map<String, dynamic>? parameters,
}) : parameters = parameters ?? {};

@override
final String name;

@override
final String description;

@override
final Map<String, dynamic> parameters;

@override
Future<String> execute(Map<String, dynamic> arguments) async => 'result';
}

void main() {
late ToolRegistry registry;

setUp(() {
registry = ToolRegistry();
});

group('ToolRegistry', () {
test('register and retrieve a tool', () {
final tool = FakeTool(name: 'test_tool');
registry.register(tool);

expect(registry.getTool('test_tool'), same(tool));
expect(registry.enabledTools, hasLength(1));
});

test('unregister a tool', () {
registry.register(FakeTool(name: 'test_tool'));
registry.unregister('test_tool');

expect(registry.getTool('test_tool'), isNull);
expect(registry.enabledTools, isEmpty);
});

test('getTool returns null for unknown name', () {
expect(registry.getTool('nonexistent'), isNull);
});

test('duplicate registration overwrites', () {
final toolA = FakeTool(name: 'dup', description: 'first');
final toolB = FakeTool(name: 'dup', description: 'second');
registry.register(toolA);
registry.register(toolB);

expect(registry.getTool('dup')!.description, 'second');
expect(registry.enabledTools, hasLength(1));
});

test('toToolDefinitions produces correct format', () {
registry.register(
FakeTool(
name: 'weather',
description: 'Get weather info',
parameters: {
'type': 'object',
'properties': {
'city': {'type': 'string'},
},
'required': ['city'],
},
),
);

final definitions = registry.toToolDefinitions();
expect(definitions, hasLength(1));
expect(definitions[0], {
'type': 'function',
'function': {
'name': 'weather',
'description': 'Get weather info',
'parameters': {
'type': 'object',
'properties': {
'city': {'type': 'string'},
},
'required': ['city'],
},
},
});
});

test('toToolDefinitions with multiple tools', () {
registry.register(FakeTool(name: 'tool_a'));
registry.register(FakeTool(name: 'tool_b'));

final definitions = registry.toToolDefinitions();
expect(definitions, hasLength(2));
expect(definitions[0]['function']['name'], 'tool_a');
expect(definitions[1]['function']['name'], 'tool_b');
});

test('enabledTools is unmodifiable', () {
registry.register(FakeTool(name: 'tool'));
final tools = registry.enabledTools;
expect(
() => (tools as List).add(FakeTool(name: 'hack')),
throwsA(anything),
);
});
});
}
Loading