Skip to content
Open
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
4 changes: 3 additions & 1 deletion src/Auth/DeviceAuthClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
*/
final class DeviceAuthClient
{
/** Public Codex CLI OAuth client id (same as openai/codex). */
/**
* Public Codex CLI OAuth client id (same as openai/codex).
*/
public const CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';

public const DEFAULT_ISSUER = 'https://auth.openai.com';
Expand Down
70 changes: 36 additions & 34 deletions src/Catalog/ModelsDevCatalog.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ final class ModelsDevCatalog implements ModelCatalogInterface

private const CACHE_TTL = 86400; // 24h

/** @var array<string, list<ModelInfo>>|null */
/**
* @var array<string, list<ModelInfo>>|null
*/
private ?array $byProvider = null;

public function __construct(
Expand Down Expand Up @@ -80,6 +82,39 @@ public function refresh(): void
$this->load();
}

/**
* @param array<string, mixed> $payload
*
* @return array<string, list<ModelInfo>>
*/
public function parsePayload(array $payload): array
{
$catalog = [];

foreach ($payload as $providerKey => $providerData) {
if (!\is_array($providerData) || !isset($providerData['models']) || !\is_array($providerData['models'])) {
continue;
}

$providerId = (string) ($providerData['id'] ?? $providerKey);
$models = [];

foreach ($providerData['models'] as $modelKey => $modelData) {
if (!\is_array($modelData)) {
continue;
}

$models[] = $this->mapModel($providerId, (string) $modelKey, $modelData);
}

if ($models !== []) {
$catalog[$providerId] = $models;
}
}

return $catalog;
}

/**
* @return array<string, list<ModelInfo>>
*/
Expand Down Expand Up @@ -132,39 +167,6 @@ private function fetchAndParse(): array
return $this->parsePayload($payload);
}

/**
* @param array<string, mixed> $payload
*
* @return array<string, list<ModelInfo>>
*/
public function parsePayload(array $payload): array
{
$catalog = [];

foreach ($payload as $providerKey => $providerData) {
if (!\is_array($providerData) || !isset($providerData['models']) || !\is_array($providerData['models'])) {
continue;
}

$providerId = (string) ($providerData['id'] ?? $providerKey);
$models = [];

foreach ($providerData['models'] as $modelKey => $modelData) {
if (!\is_array($modelData)) {
continue;
}

$models[] = $this->mapModel($providerId, (string) $modelKey, $modelData);
}

if ($models !== []) {
$catalog[$providerId] = $models;
}
}

return $catalog;
}

/**
* @param array<string, mixed> $data
*/
Expand Down
22 changes: 11 additions & 11 deletions src/Client/AiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,17 +231,6 @@ public function isImageGenerationAvailable(?string $salesChannelId = null): bool
return $this->hasImageCapableProvider($salesChannelId);
}

private function hasImageCapableProvider(?string $salesChannelId = null): bool
{
foreach ($this->providers->configured($salesChannelId) as $provider) {
if (\in_array(Capability::ImageGeneration, $provider->getCapabilities(), true)) {
return true;
}
}

return false;
}

public function featureFlags(): FeatureFlags
{
return $this->featureFlags;
Expand Down Expand Up @@ -272,4 +261,15 @@ public function getCatalog(): ModelCatalogInterface
{
return $this->catalog;
}

private function hasImageCapableProvider(?string $salesChannelId = null): bool
{
foreach ($this->providers->configured($salesChannelId) as $provider) {
if (\in_array(Capability::ImageGeneration, $provider->getCapabilities(), true)) {
return true;
}
}

return false;
}
}
16 changes: 12 additions & 4 deletions src/Client/PromptBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
*/
final class PromptBuilder
{
/** @var list<ChatMessage> */
/**
* @var list<ChatMessage>
*/
private array $messages = [];

private ?string $provider = null;
Expand All @@ -46,7 +48,9 @@ final class PromptBuilder

private ?int $maxTokens = null;

/** @var array<string, mixed>|null */
/**
* @var array<string, mixed>|null
*/
private ?array $responseSchema = null;

private int $candidateCount = 1;
Expand All @@ -57,12 +61,16 @@ final class PromptBuilder

private string $imageResponseFormat = ImageRequest::FORMAT_B64;

/** @var list<ImageInput> */
/**
* @var list<ImageInput>
*/
private array $sourceImages = [];

private ?ImageInput $mask = null;

/** @var array<string, mixed> */
/**
* @var array<string, mixed>
*/
private array $metadata = [];

public function __construct(
Expand Down
4 changes: 3 additions & 1 deletion src/Client/ProviderRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
*/
final class ProviderRegistry
{
/** @var array<string, AiProviderInterface> */
/**
* @var array<string, AiProviderInterface>
*/
private array $providers = [];

/**
Expand Down
4 changes: 3 additions & 1 deletion src/Config/CapabilityDefaultsStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ final class CapabilityDefaultsStore
{
private const CONFIG_PREFIX = 'FroshAI.config.';

/** @var array<string, array{provider: string, model: string}> */
/**
* @var array<string, array{provider: string, model: string}>
*/
private const KEYS = [
'text_generation' => [
'provider' => 'defaultTextProvider',
Expand Down
12 changes: 9 additions & 3 deletions src/Entity/BulkJob/BulkJobEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@ class BulkJobEntity extends Entity

protected int $failed = 0;

/** @var list<string>|null */
/**
* @var list<string>|null
*/
protected ?array $entityIds = null;

/** @var list<array{entityId: string, message: string}>|null */
/**
* @var list<array{entityId: string, message: string}>|null
*/
protected ?array $errors = null;

/** @var array<string, mixed>|null */
/**
* @var array<string, mixed>|null
*/
protected ?array $context = null;

public function getType(): string
Expand Down
4 changes: 3 additions & 1 deletion src/Entity/UsageLog/UsageLogEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ class UsageLogEntity extends Entity

protected ?string $errorMessage = null;

/** @var array<string, mixed>|null */
/**
* @var array<string, mixed>|null
*/
protected ?array $metadata = null;

public function getType(): string
Expand Down
4 changes: 2 additions & 2 deletions src/Provider/Anthropic/AnthropicProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ final class AnthropicProvider implements AiProviderInterface
{
public const ID = 'anthropic';

private const CONFIG_PREFIX = 'FroshAI.config.';

public const DEFAULT_BASE_URL = 'https://api.anthropic.com';

public const DEFAULT_TEXT_MODEL = 'claude-sonnet-4-5';

private const CONFIG_PREFIX = 'FroshAI.config.';

public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly SystemConfigService $systemConfigService,
Expand Down
1 change: 0 additions & 1 deletion src/Provider/Gemini/GeminiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Frosh\AI\Enum\FinishReason;
use Frosh\AI\Enum\MessageRole;
use Frosh\AI\Exception\ProviderHttpException;
use Frosh\AI\Message\ChatMessage;
use Frosh\AI\Request\ChatRequest;
use Frosh\AI\Request\ImageRequest;
use Frosh\AI\Response\ChatResponse;
Expand Down
4 changes: 2 additions & 2 deletions src/Provider/Gemini/GeminiProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ final class GeminiProvider implements AiProviderInterface
{
public const ID = 'gemini';

private const CONFIG_PREFIX = 'FroshAI.config.';

public const DEFAULT_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';

public const DEFAULT_TEXT_MODEL = 'gemini-2.5-flash';

public const DEFAULT_IMAGE_MODEL = 'gemini-2.5-flash-image';

private const CONFIG_PREFIX = 'FroshAI.config.';

public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly SystemConfigService $systemConfigService,
Expand Down
4 changes: 2 additions & 2 deletions src/Provider/Mistral/MistralProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ final class MistralProvider implements AiProviderInterface
{
public const ID = 'mistral';

private const CONFIG_PREFIX = 'FroshAI.config.';

public const DEFAULT_BASE_URL = 'https://api.mistral.ai/v1';

public const DEFAULT_MODEL = 'mistral-large-latest';

private const CONFIG_PREFIX = 'FroshAI.config.';

public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly SystemConfigService $systemConfigService,
Expand Down
74 changes: 37 additions & 37 deletions src/Provider/OpenAi/OpenAiLiveModelClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,43 +54,6 @@ public function listModels(?string $preferredSource = null): array
};
}

/**
* @param list<array<string, mixed>> $models
*
* @return array{source: string, models: list<array<string, mixed>>, rawCount: int}
*/
private function wrap(string $source, array $models): array
{
return [
'source' => $source,
'models' => $models,
'rawCount' => \count($models),
];
}

/**
* @return array{source: string, models: list<array<string, mixed>>, rawCount?: int}
*/
private function listAuto(): array
{
if ($this->chatGptAuth->isConnected()) {
try {
return $this->wrap('chatgpt', $this->listChatGptModels());
} catch (\Throwable $e) {
$this->logger->warning('FroshAI: ChatGPT models fetch failed: {message}', [
'message' => $e->getMessage(),
'exception' => $e,
]);
}
}

if ($this->apiKey() !== null) {
return $this->wrap('api_key', $this->listApiKeyModels());
}

throw new AiException('No live model source available. Connect ChatGPT or configure an API key.');
}

/**
* Models available on the ChatGPT / Codex subscription backend.
*
Expand Down Expand Up @@ -335,6 +298,43 @@ public function mapApiKeyModels(array $payload): array
return $rows;
}

/**
* @param list<array<string, mixed>> $models
*
* @return array{source: string, models: list<array<string, mixed>>, rawCount: int}
*/
private function wrap(string $source, array $models): array
{
return [
'source' => $source,
'models' => $models,
'rawCount' => \count($models),
];
}

/**
* @return array{source: string, models: list<array<string, mixed>>, rawCount?: int}
*/
private function listAuto(): array
{
if ($this->chatGptAuth->isConnected()) {
try {
return $this->wrap('chatgpt', $this->listChatGptModels());
} catch (\Throwable $e) {
$this->logger->warning('FroshAI: ChatGPT models fetch failed: {message}', [
'message' => $e->getMessage(),
'exception' => $e,
]);
}
}

if ($this->apiKey() !== null) {
return $this->wrap('api_key', $this->listApiKeyModels());
}

throw new AiException('No live model source available. Connect ChatGPT or configure an API key.');
}

private function apiKey(): ?string
{
$value = $this->systemConfigService->getString('FroshAI.config.openAiApiKey');
Expand Down
Loading
Loading