---
title: "Client"
description: "Generate text or structured JSON from any PHP context with one method call – CLI commands, hooks, custom controllers."
canonical_url: "https://kirby.tools/docs/copilot/php-classes/client"
---

# Client

> Generate text or structured JSON from any PHP context with one method call – CLI commands, hooks, custom controllers.

Generate AI content from PHP with one method call. The `Client` resolves your configured provider from `johannschopplich.copilot` options and dispatches text or structured-output requests – reaching the same providers with the same credentials as the Panel, over its own server-side transport.

```php
use JohannSchopplich\Copilot\AI\Client;

$result = Client::instance()->generateObject(
    messages: [
        ['role' => 'user', 'content' => "What's 2+2?"],
    ],
    schema: ['type' => 'object', 'properties' => ['answer' => ['type' => 'integer']], 'required' => ['answer']],
);
```

## Construction

<tabs :default-value="singleton">
<tabs-item label="Singleton" value="singleton">

```php
use JohannSchopplich\Copilot\AI\Client;

$client = Client::instance();
```

The singleton reads `johannschopplich.copilot` options once and reuses them until you call `Client::reset()`.

</tabs-item>

<tabs-item label="Manual" value="manual">

```php
use JohannSchopplich\Copilot\AI\Client;
use JohannSchopplich\Copilot\AI\Resolver;

$client = new Client(
    resolver: Resolver::fromKirbyOptions(),
);
```

A fresh resolution against changed config – useful when options are swapped at runtime, for example in tests.

</tabs-item>

<tabs-item label="Override" value="override">

```php
use JohannSchopplich\Copilot\AI\Client;
use JohannSchopplich\Copilot\AI\Providers\OpenAIProvider;
use JohannSchopplich\Copilot\AI\ProviderConfig;

$provider = new OpenAIProvider(new ProviderConfig(
    apiKey: 'sk-test-xxx',
    model: 'gpt-5.6-luna',
));

$client = new Client(providerOverride: $provider);
```

Bypass the resolver entirely. Intended for tests and one-off scripts.

</tabs-item>
</tabs>

## Methods

### `generateObject`

```php
public function generateObject(array $messages, array $schema): array
```

<field-group>
<field name="messages" type="list<array{role: string, content: string}>">

Chat messages. Every `system` message is forwarded – Anthropic concatenates them into its top-level `system` field, while the OpenAI-compatible providers pass the list through unchanged.

</field>

<field name="schema" type="array<string, mixed>">

JSON Schema for the response. The provider enforces it via the appropriate structured-output mechanism for its API.

</field>
</field-group>

Returns the decoded JSON response as an associative array. Throws [`ProviderException`](/docs/copilot/php-classes/exceptions) on any failure.

### `generateText`

```php
public function generateText(array $messages): string
```

<field-group>
<field name="messages" type="list<array{role: string, content: string}>">

Chat messages. Every `system` message is forwarded – Anthropic concatenates them into its top-level `system` field, while the OpenAI-compatible providers pass the list through unchanged.

</field>
</field-group>

Returns the response text as a plain string. Throws [`ProviderException`](/docs/copilot/php-classes/exceptions) on any failure.

### `requireApiKey`

```php
public function requireApiKey(): void
```

Asserts that the configured default provider has an API key. Throws `Kirby\Exception\AuthException` with a message pointing to the missing config path. Useful as a preflight before kicking off a long batch operation.

The check applies to the resolved default provider only. A client constructed with an explicit provider instance returns early, since that provider carries its own credentials – the key check then happens on the first request.

### `reset` (static)

```php
public static function reset(): void
```

Clear the cached singleton. Call this in tests that swap config between cases.

## Error Handling

```php
use JohannSchopplich\Copilot\AI\Client;
use JohannSchopplich\Copilot\AI\Exception\ProviderException;
use Kirby\Exception\AuthException;

try {
    $client = Client::instance();
    $client->requireApiKey();

    $result = $client->generateObject($messages, $schema);
} catch (AuthException $error) {
    // Missing API key
} catch (ProviderException $error) {
    // Upstream failure – $error->getDetails() carries provider/model/response context
}
```

<callout color="info" icon="i-ri-arrow-right-line" to="/docs/copilot/php-classes/exceptions">

See **Exceptions** for the full `ProviderException` payload.

</callout>

## Forcing a Provider for One Call

The resolver always dispatches to the provider set at `johannschopplich.copilot.provider`. To force a different one for a single call, use `providerOverride`:

```php
use JohannSchopplich\Copilot\AI\Client;
use JohannSchopplich\Copilot\AI\Providers\AnthropicProvider;
use JohannSchopplich\Copilot\AI\Resolver;
use JohannSchopplich\Copilot\AI\ProviderName;

$config = Resolver::fromKirbyOptions()->forProvider(ProviderName::Anthropic);
$client = new Client(providerOverride: new AnthropicProvider($config));

$result = $client->generateObject($messages, $schema);
```

## Use From a Sister Plugin

The Copilot AI strategy in [Content Translator](/docs/content-translator) is a working example of consuming `Client::generateObject()` from another plugin. See [the AI Strategy CLI script](/docs/content-translator/cli-automation/ai-strategy) for a complete page-translation workflow that selects the AI strategy at call time.

---

Every page of this site as Markdown: <https://kirby.tools/sitemap.md>
