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
7 changes: 7 additions & 0 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

use BackedEnum;
use Gemini\Contracts\ClientContract;
use Gemini\Contracts\Resources\CachedContentsContract;
use Gemini\Contracts\Resources\FilesContract;
use Gemini\Contracts\Resources\GenerativeModelContract;
use Gemini\Contracts\TransporterContract;
use Gemini\Enums\ModelType;
use Gemini\Resources\CachedContents;
use Gemini\Resources\ChatSession;
use Gemini\Resources\EmbeddingModel;
use Gemini\Resources\Files;
Expand Down Expand Up @@ -74,4 +76,9 @@ public function files(): FilesContract
{
return new Files($this->transporter);
}

public function cachedContents(): CachedContentsContract
{
return new CachedContents($this->transporter);
}
}
3 changes: 3 additions & 0 deletions src/Contracts/ClientContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Gemini\Contracts;

use BackedEnum;
use Gemini\Contracts\Resources\CachedContentsContract;
use Gemini\Contracts\Resources\ChatSessionContract;
use Gemini\Contracts\Resources\EmbeddingModalContract;
use Gemini\Contracts\Resources\FilesContract;
Expand Down Expand Up @@ -32,4 +33,6 @@ public function embeddingModel(BackedEnum|string $model): EmbeddingModalContract
public function chat(BackedEnum|string $model): ChatSessionContract;

public function files(): FilesContract;

public function cachedContents(): CachedContentsContract;
}
39 changes: 39 additions & 0 deletions src/Contracts/Resources/CachedContentsContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Gemini\Contracts\Resources;

use BackedEnum;
use Gemini\Data\Blob;
use Gemini\Data\Content;
use Gemini\Data\Tool;
use Gemini\Data\ToolConfig;
use Gemini\Data\UploadedFile;
use Gemini\Responses\CachedContents\ListResponse;
use Gemini\Responses\CachedContents\MetadataResponse;

interface CachedContentsContract
{
/**
* @param array<Tool> $tools
* @param string|Blob|array<string|Blob|UploadedFile>|Content|UploadedFile ...$parts
*/
public function create(
BackedEnum|string $model,
?Content $systemInstruction = null,
array $tools = [],
?ToolConfig $toolConfig = null,
?string $ttl = null,
?string $displayName = null,
string|Blob|array|Content|UploadedFile ...$parts,
): MetadataResponse;

public function retrieve(string $name): MetadataResponse;

public function list(?int $pageSize = null, ?string $pageToken = null): ListResponse;

public function update(string $name, ?string $ttl = null, ?string $expireTime = null): MetadataResponse;

public function delete(string $name): void;
}
109 changes: 109 additions & 0 deletions src/Data/CachedContent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace Gemini\Data;

use Gemini\Contracts\Arrayable;
use InvalidArgumentException;

/**
* Metadata about cached content.
*
* @link https://ai.google.dev/api/caching#CachedContent
*/
final class CachedContent implements Arrayable
{
public function __construct(
public readonly string $name,
public readonly string $model,
public readonly ?string $displayName,
public readonly UsageMetadata $usageMetadata,
public readonly string $createTime,
public readonly string $updateTime,
public readonly string $expireTime,
) {}

/**
* @param array<string, mixed> $attributes
*/
public static function from(array $attributes): self
{

if (! is_string($attributes['name'] ?? null)) {
throw new InvalidArgumentException('Name must be a string');
}

if (! is_string($attributes['model'] ?? null)) {
throw new InvalidArgumentException('Model must be a string');
}

if (isset($attributes['displayName']) && ! is_string($attributes['displayName'])) {
throw new InvalidArgumentException('DisplayName must be a string');
}

if (! is_array($attributes['usageMetadata'] ?? null)) {
throw new InvalidArgumentException('UsageMetadata must be an array');
}

if (! is_string($attributes['createTime'] ?? null)) {
throw new InvalidArgumentException('CreateTime must be a string');
}

if (! is_string($attributes['updateTime'] ?? null)) {
throw new InvalidArgumentException('UpdateTime must be a string');
}

if (! is_string($attributes['expireTime'] ?? null)) {
throw new InvalidArgumentException('ExpireTime must be a string');
}

$name = $attributes['name'];
$model = $attributes['model'];
/** @var string|null $displayName */
$displayName = $attributes['displayName'] ?? null;

/**
* @var array{
* promptTokenCount: int,
* totalTokenCount: int,
* candidatesTokenCount: ?int,
* cachedContentTokenCount: ?int,
* toolUsePromptTokenCount: ?int,
* thoughtsTokenCount: ?int,
* promptTokensDetails: list<array{modality: string, tokenCount: int}>|null,
* cacheTokensDetails: list<array{modality: string, tokenCount: int}>|null,
* candidatesTokensDetails: list<array{modality: string, tokenCount: int}>|null,
* toolUsePromptTokensDetails: list<array{modality: string, tokenCount: int}>|null
* } $usageMetadataData
*/
$usageMetadataData = $attributes['usageMetadata'];
$usageMetadata = UsageMetadata::from($usageMetadataData);
$createTime = $attributes['createTime'];
$updateTime = $attributes['updateTime'];
$expireTime = $attributes['expireTime'];

return new self(
name: $name,
model: $model,
displayName: $displayName,
usageMetadata: $usageMetadata,
createTime: $createTime,
updateTime: $updateTime,
expireTime: $expireTime,
);
}

public function toArray(): array
{
return [
'name' => $this->name,
'model' => $this->model,
'displayName' => $this->displayName,
'usageMetadata' => $this->usageMetadata->toArray(),
'createTime' => $this->createTime,
'updateTime' => $this->updateTime,
'expireTime' => $this->expireTime,
];
}
}
5 changes: 3 additions & 2 deletions src/Data/UsageMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ public function __construct(
) {}

/**
* @param array{ promptTokenCount: int, totalTokenCount: int, candidatesTokenCount: ?int, cachedContentTokenCount: ?int, toolUsePromptTokenCount: ?int, thoughtsTokenCount: ?int, promptTokensDetails: list<array{ modality: string, tokenCount: int}>|null, cacheTokensDetails: list<array{ modality: string, tokenCount: int}>|null, candidatesTokensDetails: list<array{ modality: string, tokenCount: int}>|null, toolUsePromptTokensDetails: list<array{ modality: string, tokenCount: int}>|null } $attributes
* @param array{ promptTokenCount?: int, totalTokenCount: int, candidatesTokenCount: ?int, cachedContentTokenCount: ?int, toolUsePromptTokenCount: ?int, thoughtsTokenCount: ?int, promptTokensDetails: list<array{ modality: string, tokenCount: int}>|null, cacheTokensDetails: list<array{ modality: string, tokenCount: int}>|null, candidatesTokensDetails: list<array{ modality: string, tokenCount: int}>|null, toolUsePromptTokensDetails: list<array{ modality: string, tokenCount: int}>|null } $attributes
*/
public static function from(array $attributes): self
{
return new self(
promptTokenCount: $attributes['promptTokenCount'],
// Cache creation operations may not involve LLM interactions, resulting in incomplete usage metadata
promptTokenCount: $attributes['promptTokenCount'] ?? 0,
totalTokenCount: $attributes['totalTokenCount'],
candidatesTokenCount: $attributes['candidatesTokenCount'] ?? null,
cachedContentTokenCount: $attributes['cachedContentTokenCount'] ?? null,
Expand Down
1 change: 1 addition & 0 deletions src/Enums/Method.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ enum Method: string
{
case GET = 'GET';
case POST = 'POST';
case PATCH = 'PATCH';
case PUT = 'PUT';
case DELETE = 'DELETE';
}
6 changes: 4 additions & 2 deletions src/Foundation/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function toRequest(string $baseUrl, array $headers = [], array $queryPara

$body = null;

if ($this->method === Method::POST) {
if (in_array($this->method, [Method::POST, Method::PATCH, Method::PUT], true)) {
$parameters = match (true) {
method_exists($this, 'body') => $this->body(),
default => [],
Expand All @@ -82,7 +82,9 @@ public function toRequest(string $baseUrl, array $headers = [], array $queryPara
$request = $psr17Factory->createRequest($this->method->value, $uri);

if ($body instanceof StreamInterface) {
$request = $request->withBody($body);
$request = $request
->withHeader('Content-Type', 'application/json')
->withBody($body);
}

foreach ($headers as $name => $value) {
Expand Down
62 changes: 62 additions & 0 deletions src/Requests/CachedContents/CreateRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Gemini\Requests\CachedContents;

use Gemini\Concerns\HasContents;
use Gemini\Data\Blob;
use Gemini\Data\Content;
use Gemini\Data\Tool;
use Gemini\Data\ToolConfig;
use Gemini\Data\UploadedFile;
use Gemini\Enums\Method;
use Gemini\Foundation\Request;
use Gemini\Requests\Concerns\HasJsonBody;

class CreateRequest extends Request
{
use HasContents;
use HasJsonBody;

protected Method $method = Method::POST;

/**
* @param array<Tool> $tools
* @param array<int, string|Blob|array<string|Blob|UploadedFile>|Content|UploadedFile> $parts
*/
public function __construct(
protected readonly string $model,
protected readonly ?Content $systemInstruction = null,
protected readonly array $tools = [],
protected readonly ?ToolConfig $toolConfig = null,
protected readonly ?string $ttl = null,
protected readonly ?string $displayName = null,
/** @var array<int, string|Blob|array<string|Blob|UploadedFile>|Content|UploadedFile> */
protected array $parts = [],
) {}

public function resolveEndpoint(): string
{
return 'cachedContents';
}

/**
* @return array<string, mixed>
*/
protected function defaultBody(): array
{
return array_filter([
'model' => $this->model,
'contents' => array_map(
static fn (Content $c): array => $c->toArray(),
$this->partsToContents(...$this->parts)
),
'systemInstruction' => $this->systemInstruction?->toArray(),
'tools' => array_map(static fn (Tool $t): array => $t->toArray(), $this->tools),
'toolConfig' => $this->toolConfig?->toArray(),
'ttl' => $this->ttl,
'displayName' => $this->displayName,
], static fn ($v) => $v !== null);
}
}
30 changes: 30 additions & 0 deletions src/Requests/CachedContents/DeleteRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Gemini\Requests\CachedContents;

use Gemini\Enums\Method;
use Gemini\Foundation\Request;
use InvalidArgumentException;

class DeleteRequest extends Request
{
protected Method $method = Method::DELETE;

public function __construct(protected readonly string $name)
{
if ($name === '') {
throw new InvalidArgumentException('Name cannot be empty');
}

if (! preg_match('/^[a-zA-Z0-9\/_-]+$/', $name)) {
throw new InvalidArgumentException('Name contains invalid characters');
}
}

public function resolveEndpoint(): string
{
return $this->name;
}
}
31 changes: 31 additions & 0 deletions src/Requests/CachedContents/ListRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Gemini\Requests\CachedContents;

use Gemini\Enums\Method;
use Gemini\Foundation\Request;

class ListRequest extends Request
{
protected Method $method = Method::GET;

public function __construct(
protected readonly ?int $pageSize = null,
protected readonly ?string $pageToken = null,
) {}

public function resolveEndpoint(): string
{
return 'cachedContents';
}

public function defaultQuery(): array
{
return array_filter([
'pageSize' => $this->pageSize,
'pageToken' => $this->pageToken,
], static fn ($v) => $v !== null);
}
}
30 changes: 30 additions & 0 deletions src/Requests/CachedContents/RetrieveRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Gemini\Requests\CachedContents;

use Gemini\Enums\Method;
use Gemini\Foundation\Request;
use InvalidArgumentException;

class RetrieveRequest extends Request
{
protected Method $method = Method::GET;

public function __construct(protected readonly string $name)
{
if ($name === '') {
throw new InvalidArgumentException('Name cannot be empty');
}

if (! preg_match('/^[a-zA-Z0-9\/_-]+$/', $name)) {
throw new InvalidArgumentException('Name contains invalid characters');
}
}

public function resolveEndpoint(): string
{
return $this->name;
}
}
Loading