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 src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ public function createSession(SessionConfig|array $config = []): CopilotSession
$response = $this->rpcClient->request('session.create', array_filter([
'sessionId' => $config['sessionId'] ?? null,
'model' => $config['model'] ?? null,
'reasoningEffort' => $config['reasoningEffort'] ?? null,
'tools' => $toolsForRequest ?: null,
'systemMessage' => $config['systemMessage'] ?? null,
'availableTools' => $config['availableTools'] ?? null,
Expand Down Expand Up @@ -309,6 +310,7 @@ public function resumeSession(string $sessionId, ResumeSessionConfig|array $conf

$response = $this->rpcClient->request('session.resume', array_filter([
'sessionId' => $sessionId,
'reasoningEffort' => $config['reasoningEffort'] ?? null,
'tools' => $toolsForRequest ?: null,
'provider' => $config['provider'] ?? null,
'requestPermission' => isset($config['onPermissionRequest']),
Expand Down
19 changes: 19 additions & 0 deletions src/Enums/ReasoningEffort.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Revolution\Copilot\Enums;

/**
* Valid reasoning effort levels for models that support it.
*
* This controls the computational effort level that models use when generating responses.
* Only valid for models where capabilities.supports.reasoningEffort is true.
*/
enum ReasoningEffort: string
{
case LOW = 'low';
case MEDIUM = 'medium';
case HIGH = 'high';
case XHIGH = 'xhigh';
}
8 changes: 8 additions & 0 deletions src/Types/ModelCapabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public function supportsVision(): bool
return $this->supports['vision'] ?? false;
}

/**
* Check if reasoning effort is supported.
*/
public function supportsReasoningEffort(): bool
{
return $this->supports['reasoningEffort'] ?? false;
}

/**
* Get max context window tokens.
*/
Expand Down
22 changes: 21 additions & 1 deletion src/Types/ModelInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Revolution\Copilot\Types;

use Illuminate\Contracts\Support\Arrayable;
use Revolution\Copilot\Enums\ReasoningEffort;

/**
* Information about an available model.
Expand All @@ -22,21 +23,34 @@ public function __construct(
public ?ModelPolicy $policy = null,
/** Billing information */
public ?ModelBilling $billing = null,
/** Supported reasoning effort levels (only present if model supports reasoning effort) */
public ?array $supportedReasoningEfforts = null,
/** Default reasoning effort level (only present if model supports reasoning effort) */
public ReasoningEffort|string|null $defaultReasoningEffort = null,
) {}

/**
* Create from array.
*
* @param array{id: string, name: string, capabilities: array, policy?: array, billing?: array} $data
* @param array{id: string, name: string, capabilities: array, policy?: array, billing?: array, supportedReasoningEfforts?: array, defaultReasoningEffort?: string} $data
*/
public static function fromArray(array $data): self
{
$defaultReasoningEffort = null;
if (isset($data['defaultReasoningEffort'])) {
$defaultReasoningEffort = is_string($data['defaultReasoningEffort'])
? $data['defaultReasoningEffort']
: $data['defaultReasoningEffort'];
}

return new self(
id: $data['id'],
name: $data['name'],
capabilities: ModelCapabilities::fromArray($data['capabilities']),
policy: isset($data['policy']) ? ModelPolicy::fromArray($data['policy']) : null,
billing: isset($data['billing']) ? ModelBilling::fromArray($data['billing']) : null,
supportedReasoningEfforts: $data['supportedReasoningEfforts'] ?? null,
defaultReasoningEffort: $defaultReasoningEffort,
);
}

Expand All @@ -45,12 +59,18 @@ public static function fromArray(array $data): self
*/
public function toArray(): array
{
$defaultReasoningEffort = $this->defaultReasoningEffort instanceof ReasoningEffort
? $this->defaultReasoningEffort->value
: $this->defaultReasoningEffort;

return array_filter([
'id' => $this->id,
'name' => $this->name,
'capabilities' => $this->capabilities->toArray(),
'policy' => $this->policy?->toArray(),
'billing' => $this->billing?->toArray(),
'supportedReasoningEfforts' => $this->supportedReasoningEfforts,
'defaultReasoningEffort' => $defaultReasoningEffort,
], fn ($v) => $v !== null);
}
}
20 changes: 20 additions & 0 deletions src/Types/ResumeSessionConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@

use Closure;
use Illuminate\Contracts\Support\Arrayable;
use Revolution\Copilot\Enums\ReasoningEffort;

/**
* Configuration for resuming a session.
*/
readonly class ResumeSessionConfig implements Arrayable
{
public function __construct(
/**
* Reasoning effort level for models that support it.
* Only valid for models where capabilities.supports.reasoningEffort is true.
* Accepts either ReasoningEffort enum or string value.
*/
public ReasoningEffort|string|null $reasoningEffort = null,
/**
* Tools exposed to the CLI server.
*/
Expand Down Expand Up @@ -79,6 +86,13 @@ public function __construct(
*/
public static function fromArray(array $data): self
{
$reasoningEffort = null;
if (isset($data['reasoningEffort'])) {
$reasoningEffort = $data['reasoningEffort'] instanceof ReasoningEffort
? $data['reasoningEffort']
: $data['reasoningEffort'];
}

$provider = null;
if (isset($data['provider'])) {
$provider = $data['provider'] instanceof ProviderConfig
Expand All @@ -94,6 +108,7 @@ public static function fromArray(array $data): self
}

return new self(
reasoningEffort: $reasoningEffort,
tools: $data['tools'] ?? null,
provider: $provider,
onPermissionRequest: $data['onPermissionRequest'] ?? null,
Expand All @@ -114,6 +129,10 @@ public static function fromArray(array $data): self
*/
public function toArray(): array
{
$reasoningEffort = $this->reasoningEffort instanceof ReasoningEffort
? $this->reasoningEffort->value
: $this->reasoningEffort;

$provider = $this->provider instanceof ProviderConfig
? $this->provider->toArray()
: $this->provider;
Expand All @@ -123,6 +142,7 @@ public function toArray(): array
: $this->hooks;

return array_filter([
'reasoningEffort' => $reasoningEffort,
'tools' => $this->tools,
'provider' => $provider,
'onPermissionRequest' => $this->onPermissionRequest,
Expand Down
21 changes: 21 additions & 0 deletions src/Types/SessionConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Closure;
use Illuminate\Contracts\Support\Arrayable;
use Revolution\Copilot\Enums\ReasoningEffort;

/**
* Configuration for creating a session.
Expand All @@ -22,6 +23,13 @@ public function __construct(
* Model to use for this session.
*/
public ?string $model = null,
/**
* Reasoning effort level for models that support it.
* Only valid for models where capabilities.supports.reasoningEffort is true.
* Use client.listModels() to check supported values for each model.
* Accepts either ReasoningEffort enum or string value.
*/
public ReasoningEffort|string|null $reasoningEffort = null,
/**
* Override the default configuration directory location.
* When specified, the session will use this directory for storing config and state.
Expand Down Expand Up @@ -135,9 +143,17 @@ public static function fromArray(array $data): self
: SessionHooks::fromArray($data['hooks']);
}

$reasoningEffort = null;
if (isset($data['reasoningEffort'])) {
$reasoningEffort = $data['reasoningEffort'] instanceof ReasoningEffort
? $data['reasoningEffort']
: $data['reasoningEffort'];
}

return new self(
sessionId: $data['sessionId'] ?? null,
model: $data['model'] ?? null,
reasoningEffort: $reasoningEffort,
configDir: $data['configDir'] ?? null,
tools: $data['tools'] ?? null,
systemMessage: $systemMessage,
Expand All @@ -162,6 +178,10 @@ public static function fromArray(array $data): self
*/
public function toArray(): array
{
$reasoningEffort = $this->reasoningEffort instanceof ReasoningEffort
? $this->reasoningEffort->value
: $this->reasoningEffort;

$systemMessage = $this->systemMessage instanceof SystemMessageConfig
? $this->systemMessage->toArray()
: $this->systemMessage;
Expand All @@ -181,6 +201,7 @@ public function toArray(): array
return array_filter([
'sessionId' => $this->sessionId,
'model' => $this->model,
'reasoningEffort' => $reasoningEffort,
'configDir' => $this->configDir,
'tools' => $this->tools,
'systemMessage' => $systemMessage,
Expand Down
31 changes: 31 additions & 0 deletions tests/Unit/Enums/ReasoningEffortTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

use Revolution\Copilot\Enums\ReasoningEffort;

describe('ReasoningEffort', function () {
it('has correct string values', function () {
expect(ReasoningEffort::LOW->value)->toBe('low')
->and(ReasoningEffort::MEDIUM->value)->toBe('medium')
->and(ReasoningEffort::HIGH->value)->toBe('high')
->and(ReasoningEffort::XHIGH->value)->toBe('xhigh');
});

it('can be created from string', function () {
expect(ReasoningEffort::from('low'))->toBe(ReasoningEffort::LOW)
->and(ReasoningEffort::from('medium'))->toBe(ReasoningEffort::MEDIUM)
->and(ReasoningEffort::from('high'))->toBe(ReasoningEffort::HIGH)
->and(ReasoningEffort::from('xhigh'))->toBe(ReasoningEffort::XHIGH);
});

it('has all expected cases', function () {
$cases = ReasoningEffort::cases();

expect($cases)->toHaveCount(4)
->and($cases)->toContain(ReasoningEffort::LOW)
->and($cases)->toContain(ReasoningEffort::MEDIUM)
->and($cases)->toContain(ReasoningEffort::HIGH)
->and($cases)->toContain(ReasoningEffort::XHIGH);
});
});
27 changes: 27 additions & 0 deletions tests/Unit/Types/ModelCapabilitiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,31 @@

expect($capabilities)->toBeInstanceOf(\Illuminate\Contracts\Support\Arrayable::class);
});

it('can check if reasoning effort is supported', function () {
$capabilities = ModelCapabilities::fromArray([
'supports' => ['vision' => false, 'reasoningEffort' => true],
'limits' => ['max_context_window_tokens' => 100000],
]);

expect($capabilities->supportsReasoningEffort())->toBeTrue();
});

it('returns false when reasoning effort is not supported', function () {
$capabilities = ModelCapabilities::fromArray([
'supports' => ['vision' => false, 'reasoningEffort' => false],
'limits' => ['max_context_window_tokens' => 100000],
]);

expect($capabilities->supportsReasoningEffort())->toBeFalse();
});

it('returns false when reasoningEffort key is missing', function () {
$capabilities = ModelCapabilities::fromArray([
'supports' => ['vision' => true],
'limits' => ['max_context_window_tokens' => 100000],
]);

expect($capabilities->supportsReasoningEffort())->toBeFalse();
});
});
66 changes: 66 additions & 0 deletions tests/Unit/Types/ModelInfoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

declare(strict_types=1);

use Revolution\Copilot\Enums\ReasoningEffort;
use Revolution\Copilot\Types\ModelBilling;
use Revolution\Copilot\Types\ModelCapabilities;
use Revolution\Copilot\Types\ModelInfo;
Expand Down Expand Up @@ -103,4 +104,69 @@

expect($modelInfo)->toBeInstanceOf(\Illuminate\Contracts\Support\Arrayable::class);
});

it('can handle supportedReasoningEfforts array', function () {
$modelInfo = ModelInfo::fromArray([
'id' => 'reasoning-model',
'name' => 'Reasoning Model',
'capabilities' => [
'supports' => ['vision' => false, 'reasoningEffort' => true],
'limits' => ['max_context_window_tokens' => 128000],
],
'supportedReasoningEfforts' => ['low', 'medium', 'high'],
'defaultReasoningEffort' => 'medium',
]);

expect($modelInfo->supportedReasoningEfforts)->toBe(['low', 'medium', 'high'])
->and($modelInfo->defaultReasoningEffort)->toBe('medium');
});

it('can handle defaultReasoningEffort as enum', function () {
$modelInfo = new ModelInfo(
id: 'reasoning-model',
name: 'Reasoning Model',
capabilities: new ModelCapabilities(
supports: ['reasoningEffort' => true],
limits: ['max_context_window_tokens' => 128000],
),
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
defaultReasoningEffort: ReasoningEffort::HIGH,
);

expect($modelInfo->defaultReasoningEffort)->toBe(ReasoningEffort::HIGH);
});

it('converts defaultReasoningEffort enum to string in toArray', function () {
$modelInfo = new ModelInfo(
id: 'reasoning-model',
name: 'Reasoning Model',
capabilities: new ModelCapabilities(
supports: ['reasoningEffort' => true],
limits: ['max_context_window_tokens' => 128000],
),
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
defaultReasoningEffort: ReasoningEffort::XHIGH,
);

$array = $modelInfo->toArray();

expect($array['defaultReasoningEffort'])->toBe('xhigh')
->and($array['supportedReasoningEfforts'])->toBe(['low', 'medium', 'high', 'xhigh']);
});

it('excludes reasoning effort fields when null', function () {
$modelInfo = new ModelInfo(
id: 'basic-model',
name: 'Basic Model',
capabilities: new ModelCapabilities(
supports: ['vision' => false],
limits: ['max_context_window_tokens' => 100000],
),
);

$array = $modelInfo->toArray();

expect($array)->not->toHaveKey('supportedReasoningEfforts')
->and($array)->not->toHaveKey('defaultReasoningEffort');
});
});
Loading