VOOZH about

URL: https://deepwiki.com/guanguans/ai-commit/5.4-adding-new-generators

⇱ Adding New Generators | guanguans/ai-commit | DeepWiki


Loading...
Menu

Adding New Generators

This document provides a step-by-step guide for implementing custom AI generators in the ai-commit system. It covers creating generator classes, registering them with the GeneratorManager, configuring generator-specific settings, and testing new generators. For architectural details of the generator system, see Generator Architecture. For implementation patterns of existing generators, see API-Based Generators and CLI-Based Generators.

Overview

The ai-commit system uses a driver pattern for AI generators, allowing easy extension with new AI services. New generators must inherit from AbstractGenerator and implement the generate() method. There are two primary integration patterns:

PatternCommunication MethodExamplesKey Classes
HTTP-basedREST API calls via GuzzleOpenAI, Moonshot, ERNIE BotAbstractClient, HTTP response handling
CLI-basedExternal binary execution via Symfony ProcessBito CLI, GitHub CopilotProcess, stdin/stdout handling

Sources: app/Generators/OpenAIGenerator.php22-88 app/Generators/GithubCopilotCliGenerator.php18-29 app/Generators/BitoCliGenerator.php18-33

Generator Implementation Workflow

GeneratorManager resolves generator classes automatically by naming convention — no new methods need to be added to it. The steps are: create the class, add config, done.

Driver name → class name resolution (app/GeneratorManager.php69-74):

str($config->get('driver', $driver))
 ->replace('openai', 'OpenAI')
 ->studly()
Config key (generators.*)driver valueResolved class
openai_chatopenai_chatApp\Generators\OpenAIChatGenerator
bito_clibito_cliApp\Generators\BitoCliGenerator
moonshotmoonshotApp\Generators\MoonshotGenerator
my_new_apimy_new_apiApp\Generators\MyNewApiGenerator

Note: The openaiOpenAI replacement in the studly conversion is hardcoded. All other driver names follow standard StudlyCase from snake_case.

Implementation workflow:


Sources: app/GeneratorManager.php63-77 app/Generators/AbstractGenerator.php28-106 config/ai-commit.php78-219

Implementing an HTTP-Based Generator

Step 1: Create Generator Class

Create a new file in app/Generators/ that extends AbstractGenerator. The class must implement a generate() method that accepts a prompt string and returns the generated commit message.

Key implementation points from app/Generators/OpenAIGenerator.php22-88:

  • Line 22-24: Class extends AbstractGenerator and declares a client property
  • Line 26-30: Constructor receives Repository $config and initializes the HTTP client
  • Line 56-67: The generate() method constructs parameters, makes the API call, and returns formatted output
  • Line 69-72: Helper method getCompletionMessages() extracts the response text
  • Line 77-87: Optional buildWriter() method for streaming response handling

Step 2: Create HTTP Client (if needed)

For HTTP-based generators, you may need to create a corresponding client class in app/Clients/. This client handles authentication, request formatting, and API-specific logic.

Pattern from app/Generators/OpenAIGenerator.php29:

$this->openAI = new OpenAI($config->all());

The client receives the full configuration array and manages HTTP communication.

Step 3: Handle Responses

Implement response parsing to extract the commit message. The generator should handle:

  • Standard JSON responses
  • Streaming responses (optional)
  • Error responses and exceptions

Response extraction pattern from app/Generators/OpenAIGenerator.php69-72:

The getCompletionMessages() method uses Arr::get() to safely extract nested response fields with a default empty string fallback.

Sources: app/Generators/OpenAIGenerator.php22-88

Implementing a CLI-Based Generator

Step 1: Create Generator Class

CLI-based generators execute external binaries using Symfony's Process component.

Pattern from app/Generators/BitoCliGenerator.php20-32:

  • Line 22-29: Create Process instance with binary path from config
  • Line 29: Use setInput() to pass prompt via stdin
  • Line 23-31: Call mustRunProcess() to execute with error handling
  • Line 31: Extract output using getOutput()

Step 2: Configure Binary Execution

For CLI generators that pass arguments rather than stdin:

Pattern from app/Generators/GithubCopilotCliGenerator.php20-28:

  • Line 25: Use ensureWithOptions() to build command array
  • Line 25: Pass binary, subcommand, and prompt as arguments
  • Line 27: Call mustRun() with callback for output handling

Step 3: Handle Process Execution

CLI generators must handle:

  • Binary path validation
  • Process execution with timeout
  • Stdout/stderr capture
  • Exit code validation

Process configuration from app/Generators/GithubModelsCliGenerator.php22-32:

Line 27 shows passing model configuration as a command argument, while line 29 demonstrates stdin input for the prompt.

Sources: app/Generators/BitoCliGenerator.php18-33 app/Generators/GithubCopilotCliGenerator.php18-29 app/Generators/GithubModelsCliGenerator.php18-33

Configuration Structure

Adding Generator Configuration

All generator configurations must be added to config/ai-commit.php under the generators key.

HTTP-based generator configuration structure:

'generators' => [
 'your_generator' => [
 'api_key' => env('YOUR_GENERATOR_API_KEY'),
 'base_url' => 'https://api.your-service.com',
 'parameters' => [
 'model' => 'your-model-name',
 'temperature' => 0.5,
 'max_tokens' => 500,
 ],
 'http_options' => [
 'timeout' => 30,
 ],
 ],
],

CLI-based generator configuration structure:

'generators' => [
 'your_cli' => [
 'binary' => env('YOUR_CLI_PATH', 'your-cli'),
 'model' => 'model-name',
 'parameters' => [
 'timeout' => 60,
 'cwd' => null,
 'env' => null,
 ],
 ],
],

Configuration Access Pattern

From app/Generators/OpenAIGenerator.php58-61:

$parameters = [
 'prompt' => $prompt,
 'user' => Str::uuid()->toString(),
] + $this->config->get('parameters', []);

Use $this->config->get() to retrieve configuration values with default fallbacks. The config object is a Repository instance passed to the constructor.

Sources: tests/TestCase.php53-56

Registering with GeneratorManager

Automatic Class Resolution (Primary Method)

GeneratorManager::createDriver() (app/GeneratorManager.php63-77) resolves generator classes purely by convention. No changes to GeneratorManager are needed.

Resolution steps:

  1. Read ai-commit.generators.{driver} from config to get a Repository instance.
  2. Read the driver key from that repository (defaults to the driver name passed in).
  3. Apply ->replace('openai', 'OpenAI')->studly() to produce a StudlyCase name.
  4. Instantiate App\Generators\{StudlyName}Generator with the config Repository.
  5. Throw InvalidArgumentException if the class does not exist.

Sources: app/GeneratorManager.php63-77

Dynamic Extension (Alternative)

Use GeneratorManager::extend() (inherited from Illuminate\Support\Manager) to register a generator at runtime without creating a class file. This is useful for testing or one-off integrations.

Example pattern:


Custom creators registered via extend() are checked first in createDriver() (app/GeneratorManager.php65-67), before the class-name convention is applied.

Sources: app/GeneratorManager.php63-77

Testing New Generators

Test File Structure

Create a test file in tests/Unit/Generators/ following the naming convention {GeneratorName}Test.php.

Basic test structure from tests/Unit/Generators/OpenAIGeneratorTest.php27-35:

  • Line 27-29: Use beforeEach() to set up HTTP mocking with setup_http_fake()
  • Line 31-35: Test the generate() method returns a non-empty string
  • Line 34: Assert HTTP calls were made

Mocking HTTP Responses

For HTTP-based generators, use Laravel's HTTP facade mocking.

Pattern from tests/Unit/Generators/OpenAIGeneratorTest.php27-29:

The setup_http_fake() helper (defined in tests/Pest.php) mocks all HTTP requests with predefined responses for testing without network calls.

Mocking Process Execution

For CLI-based generators, test process failures.

Pattern from tests/Unit/Generators/GithubCopilotCliGeneratorTest.php28-31:

  • Line 29: Configure binary path in test config
  • Line 30-31: Expect ProcessFailedException for invalid binaries

Configuration Setup in Tests

From tests/TestCase.php53-56:


Mock API keys and binary paths in the setUp() method to avoid requiring real credentials in tests.

Sources: tests/Unit/Generators/OpenAIGeneratorTest.php27-46 tests/Unit/Generators/GithubCopilotCliGeneratorTest.php28-31 tests/TestCase.php47-58

Generator Registration and Resolution Flow

End-to-end flow from CLI invocation to generator execution:


Sources: app/GeneratorManager.php63-77 app/Commands/CommitCommand.php72-78

Complete Example: Adding a Custom Generator

This example demonstrates implementing a hypothetical "CustomAI" generator that uses an HTTP API.

File Structure

app/
 Generators/
 CustomAIGenerator.php # New generator class
 Clients/
 CustomAI.php # New HTTP client
config/
 ai-commit.php # Add configuration
tests/
 Unit/
 Generators/
 CustomAIGeneratorTest.php # Test file

Implementation Checklist

StepTaskFile(s)
1Create generator class extending AbstractGeneratorapp/Generators/CustomAIGenerator.php
2(HTTP only) Create HTTP client extending AbstractClientapp/Clients/CustomAI.php
3Implement generate() methodapp/Generators/CustomAIGenerator.php
4Add configuration entry under generatorsconfig/ai-commit.php
5Create test filetests/Unit/Generators/CustomAIGeneratorTest.php
6(HTTP) Mock HTTP responses in testuses setup_http_fake() helper
7Test with real credentialsManual

No changes to GeneratorManager are required. The class is resolved automatically from the driver name by createDriver().

Implementation Pattern

Generator class structure (referencing app/Generators/OpenAIGenerator.php22-88):

Key naming rule: If your config key is custom_ai, name your class App\Generators\CustomAiGenerator. The driver value in config drives the class name lookup via createDriver() (app/GeneratorManager.php69-74).

Verification Steps

  1. Configuration test: Verify config is accessible via config('ai-commit.generators.custom_ai')
  2. Driver resolution test: Verify GeneratorManager::driver('custom_ai') returns correct instance
  3. Generation test: Verify generate() returns non-empty string
  4. Error handling test: Verify exceptions are thrown for invalid responses
  5. Integration test: Run ./ai-commit commit --generator=custom_ai with real API

Sources: tests/Unit/GeneratorManagerTest.php34-41 tests/Unit/Generators/OpenAIGeneratorTest.php31-46

Common Implementation Patterns

Streaming Response Handling

For generators that support streaming output, implement a writer callback.

Pattern from app/Generators/OpenAIGenerator.php77-87:

  • Line 77-87: Define closure that processes data chunks
  • Line 80: Split response by newlines
  • Line 82: Parse JSON for each row
  • Line 83-84: Extract text and write to output

Error Handling

Both HTTP and CLI generators should handle errors gracefully:

  • HTTP generators: Catch RequestException from Guzzle
  • CLI generators: Catch ProcessFailedException from Symfony Process
  • Provide meaningful error messages to users

Configuration Validation

Validate required configuration in the generator constructor:

  • API keys for HTTP generators
  • Binary paths for CLI generators
  • Required parameters

Throw descriptive exceptions if configuration is missing or invalid.

Sources: app/Generators/OpenAIGenerator.php77-87 tests/Unit/Generators/OpenAIGeneratorTest.php37-46

Next Steps

After implementing a new generator:

  1. Update documentation: Add generator to user guide at AI Generators Overview
  2. Update configuration reference: Document settings at Generator-Specific Configuration
  3. Add to CI/CD: Ensure tests run in GitHub Actions workflows
  4. Update README: Add to supported generators list
  5. Consider contributing: Submit pull request to main repository

For implementation details of existing generators, see API-Based Generators and CLI-Based Generators.

Refresh this wiki

On this page