From 42241344b16b62debaa9fcfacb1087234ea01872 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Sun, 30 Nov 2025 21:42:44 +0100 Subject: [PATCH 01/15] feat: implement Gemini File Search APIs (Stores & Documents) --- src/Client.php | 7 + src/Contracts/ClientContract.php | 3 + .../Resources/FileSearchStoresContract.php | 69 ++++++++ .../FileSearchStores/CreateRequest.php | 42 +++++ .../FileSearchStores/DeleteRequest.php | 31 ++++ .../Documents/DeleteRequest.php | 34 ++++ .../FileSearchStores/Documents/GetRequest.php | 25 +++ .../Documents/ListRequest.php | 46 +++++ src/Requests/FileSearchStores/GetRequest.php | 25 +++ src/Requests/FileSearchStores/ListRequest.php | 45 +++++ .../FileSearchStores/UploadRequest.php | 72 ++++++++ src/Resources/FileSearchStores.php | 91 ++++++++++ .../Documents/DocumentResponse.php | 52 ++++++ .../Documents/ListResponse.php | 49 ++++++ .../FileSearchStoreResponse.php | 40 +++++ .../FileSearchStores/ListResponse.php | 49 ++++++ .../FileSearchStores/UploadResponse.php | 54 ++++++ .../Documents/DocumentResponseFixture.php | 16 ++ .../Documents/ListResponseFixture.php | 15 ++ .../FileSearchStoreResponseFixture.php | 13 ++ .../FileSearchStores/ListResponseFixture.php | 15 ++ .../UploadResponseFixture.php | 14 ++ tests/Resources/FileSearchStores.php | 162 ++++++++++++++++++ 23 files changed, 969 insertions(+) create mode 100644 src/Contracts/Resources/FileSearchStoresContract.php create mode 100644 src/Requests/FileSearchStores/CreateRequest.php create mode 100644 src/Requests/FileSearchStores/DeleteRequest.php create mode 100644 src/Requests/FileSearchStores/Documents/DeleteRequest.php create mode 100644 src/Requests/FileSearchStores/Documents/GetRequest.php create mode 100644 src/Requests/FileSearchStores/Documents/ListRequest.php create mode 100644 src/Requests/FileSearchStores/GetRequest.php create mode 100644 src/Requests/FileSearchStores/ListRequest.php create mode 100644 src/Requests/FileSearchStores/UploadRequest.php create mode 100644 src/Resources/FileSearchStores.php create mode 100644 src/Responses/FileSearchStores/Documents/DocumentResponse.php create mode 100644 src/Responses/FileSearchStores/Documents/ListResponse.php create mode 100644 src/Responses/FileSearchStores/FileSearchStoreResponse.php create mode 100644 src/Responses/FileSearchStores/ListResponse.php create mode 100644 src/Responses/FileSearchStores/UploadResponse.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/Documents/DocumentResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/Documents/ListResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/FileSearchStoreResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/ListResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FileSearchStores/UploadResponseFixture.php create mode 100644 tests/Resources/FileSearchStores.php diff --git a/src/Client.php b/src/Client.php index ecad008..f3a8d58 100644 --- a/src/Client.php +++ b/src/Client.php @@ -8,6 +8,7 @@ use Gemini\Contracts\ClientContract; use Gemini\Contracts\Resources\CachedContentsContract; use Gemini\Contracts\Resources\FilesContract; +use Gemini\Contracts\Resources\FileSearchStoresContract; use Gemini\Contracts\Resources\GenerativeModelContract; use Gemini\Contracts\TransporterContract; use Gemini\Enums\ModelType; @@ -15,6 +16,7 @@ use Gemini\Resources\ChatSession; use Gemini\Resources\EmbeddingModel; use Gemini\Resources\Files; +use Gemini\Resources\FileSearchStores; use Gemini\Resources\GenerativeModel; use Gemini\Resources\Models; @@ -81,4 +83,9 @@ public function cachedContents(): CachedContentsContract { return new CachedContents($this->transporter); } + + public function fileSearchStores(): FileSearchStoresContract + { + return new FileSearchStores($this->transporter); + } } diff --git a/src/Contracts/ClientContract.php b/src/Contracts/ClientContract.php index 356f621..481ef01 100644 --- a/src/Contracts/ClientContract.php +++ b/src/Contracts/ClientContract.php @@ -9,6 +9,7 @@ use Gemini\Contracts\Resources\ChatSessionContract; use Gemini\Contracts\Resources\EmbeddingModalContract; use Gemini\Contracts\Resources\FilesContract; +use Gemini\Contracts\Resources\FileSearchStoresContract; use Gemini\Contracts\Resources\GenerativeModelContract; use Gemini\Contracts\Resources\ModelContract; @@ -35,4 +36,6 @@ public function chat(BackedEnum|string $model): ChatSessionContract; public function files(): FilesContract; public function cachedContents(): CachedContentsContract; + + public function fileSearchStores(): FileSearchStoresContract; } diff --git a/src/Contracts/Resources/FileSearchStoresContract.php b/src/Contracts/Resources/FileSearchStoresContract.php new file mode 100644 index 0000000..c75a878 --- /dev/null +++ b/src/Contracts/Resources/FileSearchStoresContract.php @@ -0,0 +1,69 @@ + + */ + public function defaultBody(): array + { + $body = []; + + if ($this->displayName !== null) { + $body['displayName'] = $this->displayName; + } + + return $body; + } +} diff --git a/src/Requests/FileSearchStores/DeleteRequest.php b/src/Requests/FileSearchStores/DeleteRequest.php new file mode 100644 index 0000000..45a696c --- /dev/null +++ b/src/Requests/FileSearchStores/DeleteRequest.php @@ -0,0 +1,31 @@ +name; + } + + public function defaultQuery(): array + { + return $this->force ? ['force' => 'true'] : []; + } +} diff --git a/src/Requests/FileSearchStores/Documents/DeleteRequest.php b/src/Requests/FileSearchStores/Documents/DeleteRequest.php new file mode 100644 index 0000000..fa8825d --- /dev/null +++ b/src/Requests/FileSearchStores/Documents/DeleteRequest.php @@ -0,0 +1,34 @@ +name; + } + + /** + * @return array + */ + public function defaultQuery(): array + { + return $this->force ? ['force' => 'true'] : []; + } +} diff --git a/src/Requests/FileSearchStores/Documents/GetRequest.php b/src/Requests/FileSearchStores/Documents/GetRequest.php new file mode 100644 index 0000000..a436a57 --- /dev/null +++ b/src/Requests/FileSearchStores/Documents/GetRequest.php @@ -0,0 +1,25 @@ +name; + } +} diff --git a/src/Requests/FileSearchStores/Documents/ListRequest.php b/src/Requests/FileSearchStores/Documents/ListRequest.php new file mode 100644 index 0000000..f275994 --- /dev/null +++ b/src/Requests/FileSearchStores/Documents/ListRequest.php @@ -0,0 +1,46 @@ +pageSize !== null) { + $query['pageSize'] = $this->pageSize; + } + + if ($this->nextPageToken !== null) { + $query['pageToken'] = $this->nextPageToken; + } + + return $query; + } + + public function resolveEndpoint(): string + { + return $this->storeName.'/documents'; + } +} diff --git a/src/Requests/FileSearchStores/GetRequest.php b/src/Requests/FileSearchStores/GetRequest.php new file mode 100644 index 0000000..753c98f --- /dev/null +++ b/src/Requests/FileSearchStores/GetRequest.php @@ -0,0 +1,25 @@ +name; + } +} diff --git a/src/Requests/FileSearchStores/ListRequest.php b/src/Requests/FileSearchStores/ListRequest.php new file mode 100644 index 0000000..ddf001c --- /dev/null +++ b/src/Requests/FileSearchStores/ListRequest.php @@ -0,0 +1,45 @@ +pageSize !== null) { + $query['pageSize'] = $this->pageSize; + } + + if ($this->nextPageToken !== null) { + $query['pageToken'] = $this->nextPageToken; + } + + return $query; + } + + public function resolveEndpoint(): string + { + return 'fileSearchStores'; + } +} diff --git a/src/Requests/FileSearchStores/UploadRequest.php b/src/Requests/FileSearchStores/UploadRequest.php new file mode 100644 index 0000000..21c21bf --- /dev/null +++ b/src/Requests/FileSearchStores/UploadRequest.php @@ -0,0 +1,72 @@ +storeName.':uploadToFileSearchStore'; + } + + public function toRequest(string $baseUrl, array $headers = [], array $queryParams = []): RequestInterface + { + $factory = new Psr17Factory; + $boundary = rand(111111, 999999); + + $metadata = []; + if ($this->displayName) { + $metadata['displayName'] = $this->displayName; + } + if ($this->mimeType) { + $metadata['mimeType'] = $this->mimeType->value; + } + + $requestJson = json_encode($metadata); + $contents = file_get_contents($this->filename); + + $request = $factory + ->createRequest($this->method->value, str_replace('/v1', '/upload/v1', $baseUrl).$this->resolveEndpoint()) + ->withHeader('X-Goog-Upload-Protocol', 'multipart'); + foreach ($headers as $name => $value) { + $request = $request->withHeader($name, $value); + } + + $contentType = $this->mimeType instanceof MimeType ? $this->mimeType->value : 'application/octet-stream'; + + $request = $request->withHeader('Content-Type', "multipart/related; boundary={$boundary}") + ->withBody($factory->createStream(<< $response */ + $response = $this->transporter->request(new CreateRequest($displayName)); + + return FileSearchStoreResponse::from($response->data()); + } + + public function get(string $name): FileSearchStoreResponse + { + /** @var ResponseDTO $response */ + $response = $this->transporter->request(new GetRequest($name)); + + return FileSearchStoreResponse::from($response->data()); + } + + public function list(?int $pageSize = null, ?string $nextPageToken = null): ListResponse + { + /** @var ResponseDTO, nextPageToken: ?string }> $response */ + $response = $this->transporter->request(new ListRequest(pageSize: $pageSize, nextPageToken: $nextPageToken)); + + return ListResponse::from($response->data()); + } + + public function delete(string $name, bool $force = false): void + { + $this->transporter->request(new DeleteRequest($name, $force)); + } + + public function upload(string $storeName, string $filename, ?MimeType $mimeType = null, ?string $displayName = null): UploadResponse + { + $mimeType ??= MimeType::from((string) mime_content_type($filename)); + $displayName ??= $filename; + + /** @var ResponseDTO, done: bool, response?: array, error?: array }> $response */ + $response = $this->transporter->request(new UploadRequest($storeName, $filename, $displayName, $mimeType)); + + return UploadResponse::from($response->data()); + } + + public function listDocuments(string $storeName, ?int $pageSize = null, ?string $nextPageToken = null): DocumentListResponse + { + /** @var ResponseDTO, updateTime?: string, createTime?: string }>, nextPageToken: ?string }> $response */ + $response = $this->transporter->request(new ListDocumentsRequest($storeName, $pageSize, $nextPageToken)); + + return DocumentListResponse::from($response->data()); + } + + public function getDocument(string $name): DocumentResponse + { + /** @var ResponseDTO, updateTime?: string, createTime?: string }> $response */ + $response = $this->transporter->request(new GetDocumentRequest($name)); + + return DocumentResponse::from($response->data()); + } + + public function deleteDocument(string $name, bool $force = false): void + { + $this->transporter->request(new DeleteDocumentRequest($name, $force)); + } +} diff --git a/src/Responses/FileSearchStores/Documents/DocumentResponse.php b/src/Responses/FileSearchStores/Documents/DocumentResponse.php new file mode 100644 index 0000000..fbebbc5 --- /dev/null +++ b/src/Responses/FileSearchStores/Documents/DocumentResponse.php @@ -0,0 +1,52 @@ + $customMetadata + */ + public function __construct( + public readonly string $name, + public readonly ?string $displayName = null, + public readonly array $customMetadata = [], + public readonly ?string $updateTime = null, + public readonly ?string $createTime = null, + ) {} + + /** + * @param array{ name: string, displayName?: string, customMetadata?: array, updateTime?: string, createTime?: string } $attributes + */ + public static function from(array $attributes): self + { + return new self( + name: $attributes['name'], + displayName: $attributes['displayName'] ?? null, + customMetadata: $attributes['customMetadata'] ?? [], + updateTime: $attributes['updateTime'] ?? null, + createTime: $attributes['createTime'] ?? null, + ); + } + + public function toArray(): array + { + return [ + 'name' => $this->name, + 'displayName' => $this->displayName, + 'customMetadata' => $this->customMetadata, + 'updateTime' => $this->updateTime, + 'createTime' => $this->createTime, + ]; + } +} diff --git a/src/Responses/FileSearchStores/Documents/ListResponse.php b/src/Responses/FileSearchStores/Documents/ListResponse.php new file mode 100644 index 0000000..5036d3e --- /dev/null +++ b/src/Responses/FileSearchStores/Documents/ListResponse.php @@ -0,0 +1,49 @@ + $documents + */ + public function __construct( + public readonly array $documents, + public readonly ?string $nextPageToken = null, + ) {} + + /** + * @param array{ documents: ?array, updateTime?: string, createTime?: string }>, nextPageToken: ?string } $attributes + */ + public static function from(array $attributes): self + { + return new self( + documents: array_map( + fn (array $document): DocumentResponse => DocumentResponse::from($document), + $attributes['documents'] ?? [] + ), + nextPageToken: $attributes['nextPageToken'] ?? null, + ); + } + + public function toArray(): array + { + return [ + 'documents' => array_map( + fn (DocumentResponse $document): array => $document->toArray(), + $this->documents + ), + 'nextPageToken' => $this->nextPageToken, + ]; + } +} diff --git a/src/Responses/FileSearchStores/FileSearchStoreResponse.php b/src/Responses/FileSearchStores/FileSearchStoreResponse.php new file mode 100644 index 0000000..feab3a0 --- /dev/null +++ b/src/Responses/FileSearchStores/FileSearchStoreResponse.php @@ -0,0 +1,40 @@ + $this->name, + 'displayName' => $this->displayName, + ]; + } +} diff --git a/src/Responses/FileSearchStores/ListResponse.php b/src/Responses/FileSearchStores/ListResponse.php new file mode 100644 index 0000000..08035e9 --- /dev/null +++ b/src/Responses/FileSearchStores/ListResponse.php @@ -0,0 +1,49 @@ + $fileSearchStores + */ + public function __construct( + public readonly array $fileSearchStores, + public readonly ?string $nextPageToken = null, + ) {} + + /** + * @param array{ fileSearchStores: ?array, nextPageToken: ?string } $attributes + */ + public static function from(array $attributes): self + { + return new self( + fileSearchStores: array_map( + fn (array $store): FileSearchStoreResponse => FileSearchStoreResponse::from($store), + $attributes['fileSearchStores'] ?? [] + ), + nextPageToken: $attributes['nextPageToken'] ?? null, + ); + } + + public function toArray(): array + { + return [ + 'fileSearchStores' => array_map( + fn (FileSearchStoreResponse $store): array => $store->toArray(), + $this->fileSearchStores + ), + 'nextPageToken' => $this->nextPageToken, + ]; + } +} diff --git a/src/Responses/FileSearchStores/UploadResponse.php b/src/Responses/FileSearchStores/UploadResponse.php new file mode 100644 index 0000000..c94d34f --- /dev/null +++ b/src/Responses/FileSearchStores/UploadResponse.php @@ -0,0 +1,54 @@ + $metadata + * @param array|null $response + * @param array|null $error + */ + public function __construct( + public readonly string $name, + public readonly bool $done, + public readonly ?array $metadata = null, + public readonly ?array $response = null, + public readonly ?array $error = null, + ) {} + + /** + * @param array{ name: string, done: bool, metadata?: array, response?: array, error?: array } $attributes + */ + public static function from(array $attributes): self + { + return new self( + name: $attributes['name'], + done: $attributes['done'] ?? false, + metadata: $attributes['metadata'] ?? null, + response: $attributes['response'] ?? null, + error: $attributes['error'] ?? null, + ); + } + + public function toArray(): array + { + return [ + 'name' => $this->name, + 'done' => $this->done, + 'metadata' => $this->metadata, + 'response' => $this->response, + 'error' => $this->error, + ]; + } +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/Documents/DocumentResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/Documents/DocumentResponseFixture.php new file mode 100644 index 0000000..0a3f2e7 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/Documents/DocumentResponseFixture.php @@ -0,0 +1,16 @@ + 'fileSearchStores/123/documents/abc', + 'displayName' => 'My Document', + 'customMetadata' => [], + 'updateTime' => '2024-01-01T00:00:00Z', + 'createTime' => '2024-01-01T00:00:00Z', + ]; +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/Documents/ListResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/Documents/ListResponseFixture.php new file mode 100644 index 0000000..d7bcde0 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/Documents/ListResponseFixture.php @@ -0,0 +1,15 @@ + [ + DocumentResponseFixture::ATTRIBUTES, + ], + 'nextPageToken' => 'next-page-token', + ]; +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/FileSearchStoreResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/FileSearchStoreResponseFixture.php new file mode 100644 index 0000000..4ee27e2 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/FileSearchStoreResponseFixture.php @@ -0,0 +1,13 @@ + 'fileSearchStores/123-456', + 'displayName' => 'My Store', + ]; +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/ListResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/ListResponseFixture.php new file mode 100644 index 0000000..481bfa4 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/ListResponseFixture.php @@ -0,0 +1,15 @@ + [ + FileSearchStoreResponseFixture::ATTRIBUTES, + ], + 'nextPageToken' => 'next-page-token', + ]; +} diff --git a/src/Testing/Responses/Fixtures/FileSearchStores/UploadResponseFixture.php b/src/Testing/Responses/Fixtures/FileSearchStores/UploadResponseFixture.php new file mode 100644 index 0000000..0c58487 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FileSearchStores/UploadResponseFixture.php @@ -0,0 +1,14 @@ + 'operations/123-456', + 'metadata' => ['some' => 'meta'], + 'done' => false, + ]; +} diff --git a/tests/Resources/FileSearchStores.php b/tests/Resources/FileSearchStores.php new file mode 100644 index 0000000..392b418 --- /dev/null +++ b/tests/Resources/FileSearchStores.php @@ -0,0 +1,162 @@ + 'My Store'], + validateParams: true + ); + + $result = $client->fileSearchStores()->create('My Store'); + + expect($result) + ->toBeInstanceOf(FileSearchStoreResponse::class) + ->name->toBe('fileSearchStores/123-456') + ->displayName->toBe('My Store'); +}); + +test('get', function () { + $client = mockClient( + method: Method::GET, + endpoint: 'fileSearchStores/123-456', + response: FileSearchStoreResponse::fake() + ); + + $result = $client->fileSearchStores()->get('fileSearchStores/123-456'); + + expect($result) + ->toBeInstanceOf(FileSearchStoreResponse::class) + ->name->toBe('fileSearchStores/123-456'); +}); + +test('list', function () { + $client = mockClient( + method: Method::GET, + endpoint: 'fileSearchStores', + response: ListResponse::fake() + ); + + $result = $client->fileSearchStores()->list(); + + expect($result) + ->toBeInstanceOf(ListResponse::class) + ->fileSearchStores->toHaveCount(1) + ->fileSearchStores->each->toBeInstanceOf(FileSearchStoreResponse::class); +}); + +test('delete', function () { + $client = mockClient( + method: Method::DELETE, + endpoint: 'fileSearchStores/123-456', + response: new \Gemini\Transporters\DTOs\ResponseDTO([]) + ); + + $client->fileSearchStores()->delete('fileSearchStores/123-456'); + + // If no exception, it passed. + expect(true)->toBeTrue(); +}); + +test('delete with force', function () { + $client = mockClient( + method: Method::DELETE, + endpoint: 'fileSearchStores/123-456', + response: new \Gemini\Transporters\DTOs\ResponseDTO([]), + params: ['force' => 'true'], + validateParams: true + ); + + $client->fileSearchStores()->delete('fileSearchStores/123-456', true); + + expect(true)->toBeTrue(); +}); + +describe('upload', function () { + beforeEach(function () { + $this->tmpFile = tmpfile(); + $this->tmpFilepath = stream_get_meta_data($this->tmpFile)['uri']; + }); + afterEach(function () { + fclose($this->tmpFile); + }); + + test('upload', function () { + $client = mockClient( + method: Method::POST, + endpoint: 'fileSearchStores/123:uploadToFileSearchStore', + response: UploadResponse::fake(), + rootPath: '/upload/v1beta/' + ); + + $result = $client->fileSearchStores()->upload('fileSearchStores/123', $this->tmpFilepath, MimeType::TEXT_PLAIN, 'Display'); + + expect($result) + ->toBeInstanceOf(UploadResponse::class) + ->name->toBe('operations/123-456'); + }); +}); + +test('list documents', function () { + $client = mockClient( + method: Method::GET, + endpoint: 'fileSearchStores/123/documents', + response: DocumentListResponse::fake() + ); + + $result = $client->fileSearchStores()->listDocuments('fileSearchStores/123'); + + expect($result) + ->toBeInstanceOf(DocumentListResponse::class) + ->documents->toHaveCount(1) + ->documents->each->toBeInstanceOf(DocumentResponse::class); +}); + +test('get document', function () { + $client = mockClient( + method: Method::GET, + endpoint: 'fileSearchStores/123/documents/abc', + response: DocumentResponse::fake() + ); + + $result = $client->fileSearchStores()->getDocument('fileSearchStores/123/documents/abc'); + + expect($result) + ->toBeInstanceOf(DocumentResponse::class) + ->name->toBe('fileSearchStores/123/documents/abc'); +}); + +test('delete document', function () { + $client = mockClient( + method: Method::DELETE, + endpoint: 'fileSearchStores/123/documents/abc', + response: new \Gemini\Transporters\DTOs\ResponseDTO([]) + ); + + $client->fileSearchStores()->deleteDocument('fileSearchStores/123/documents/abc'); + + expect(true)->toBeTrue(); +}); + +test('delete document with force', function () { + $client = mockClient( + method: Method::DELETE, + endpoint: 'fileSearchStores/123/documents/abc', + response: new \Gemini\Transporters\DTOs\ResponseDTO([]), + params: ['force' => 'true'], + validateParams: true + ); + + $client->fileSearchStores()->deleteDocument('fileSearchStores/123/documents/abc', true); + + expect(true)->toBeTrue(); +}); From 9c8a1fa416c960e9ebbfdc4a57c51d773f19700c Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 1 Dec 2025 14:45:38 +0100 Subject: [PATCH 02/15] docs: Update README.md with File Search APIs documentation --- README.md | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/README.md b/README.md index 2601a22..38f9662 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,17 @@ - [Update Cached Content](#update-cached-content) - [Delete Cached Content](#delete-cached-content) - [Use Cached Content](#use-cached-content) + - [File Search Stores](#file-search-stores) + - [Create File Search Store](#create-file-search-store) + - [Get File Search Store](#get-file-search-store) + - [List File Search Stores](#list-file-search-stores) + - [Delete File Search Store](#delete-file-search-store) + - [Update File Search Store](#update-file-search-store) + - [File Search Documents](#file-search-documents) + - [Create File Search Document](#create-file-search-document) + - [Get File Search Document](#get-file-search-document) + - [List File Search Documents](#list-file-search-documents) + - [Delete File Search Document](#delete-file-search-document) - [Embedding Resource](#embedding-resource) - [Models](#models) - [List Models](#list-models) @@ -834,6 +845,167 @@ echo "Cached tokens used: {$response->usageMetadata->cachedContentTokenCount}\n" echo "New tokens used: {$response->usageMetadata->promptTokenCount}\n"; ``` +### File Search Stores + +File search allows you to search files that were uploaded through the File API. + +#### Create File Search Store +Create a file search store. + +```php +use Gemini\Enums\FileState; +use Gemini\Enums\MimeType; +use Gemini\Enums\Schema; +use Gemini\Enums\DataType; + +$files = $client->files(); +echo "Uploading\n"; +$meta = $files->upload( + filename: 'document.pdf', + mimeType: MimeType::APPLICATION_PDF, + displayName: 'Document for search' +); +echo "Processing"; +do { + echo "."; + sleep(2); + $meta = $files->metadataGet($meta->uri); +} while (! $meta->state->complete()); +echo "\n"; + +if ($meta->state == FileState::Failed) { + die("Upload failed:\n".json_encode($meta->toArray(), JSON_PRETTY_PRINT)); +} + +$fileSearchStore = $client->fileSearchStores()->create( + displayName: 'My Search Store', + defaultSchema: new Schema( + declarations: [ + 'name' => new Schema(type: DataType::STRING), + 'size' => new Schema(type: DataType::INTEGER), + ], + ), + defaultDocumentConfig: [ + 'files/'.basename($meta->uri), + ], +); + +echo "File search store created: {$fileSearchStore->name}\n"; +``` + +#### Get File Search Store +Get a specific file search store by name. + +```php +$fileSearchStore = $client->fileSearchStores()->retrieve('fileSearchStores/my-search-store'); + +echo "Name: {$fileSearchStore->name}\n"; +echo "Display Name: {$fileSearchStore->displayName}\n"; +``` + +#### List File Search Stores +List all file search stores. + +```php +$response = $client->fileSearchStores()->list(pageSize: 10); + +foreach ($response->fileSearchStores as $fileSearchStore) { + echo "Name: {$fileSearchStore->name}\n"; + echo "Display Name: {$fileSearchStore->displayName}\n"; + echo "Create Time: {$fileSearchStore->createTime}\n"; + echo "Update Time: {$fileSearchStore->updateTime}\n"; + echo "--- \n"; +} +``` + +#### Delete File Search Store +Delete a file search store by name. + +```php +$client->fileSearchStores()->delete('fileSearchStores/my-search-store'); +``` + +#### Update File Search Store +Update a file search store. + +```php +$fileSearchStore = $client->fileSearchStores()->update( + name: 'fileSearchStores/my-search-store', + displayName: 'My Updated Search Store', +); + +echo "File search store updated: {$fileSearchStore->name}\n"; +``` + +### File Search Documents + +#### Create File Search Document +Create a file search document within a store. + +```php +use Gemini\Enums\FileState; +use Gemini\Enums\MimeType; + +$files = $client->files(); +echo "Uploading\n"; +$meta = $files->upload( + filename: 'document2.pdf', + mimeType: MimeType::APPLICATION_PDF, + displayName: 'Another document for search' +); +echo "Processing"; +do { + echo "."; + sleep(2); + $meta = $files->metadataGet($meta->uri); +} while (! $meta->state->complete()); +echo "\n"; + +if ($meta->state == FileState::Failed) { + die("Upload failed:\n".json_encode($meta->toArray(), JSON_PRETTY_PRINT)); +} + +$fileSearchDocument = $client->fileSearchDocuments()->create( + parent: 'fileSearchStores/my-search-store', + file: 'files/'.basename($meta->uri), + displayName: 'Another Search Document', +); + +echo "File search document created: {$fileSearchDocument->name}\n"; +``` + +#### Get File Search Document +Get a specific file search document by name. + +```php +$fileSearchDocument = $client->fileSearchDocuments()->retrieve('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); + +echo "Name: {$fileSearchDocument->name}\n"; +echo "Display Name: {$fileSearchDocument->displayName}\n"; +``` + +#### List File Search Documents +List all file search documents within a store. + +```php +$response = $client->fileSearchDocuments()->list(parent: 'fileSearchStores/my-search-store', pageSize: 10); + +foreach ($response->fileSearchDocuments as $fileSearchDocument) { + echo "Name: {$fileSearchDocument->name}\n"; + echo "Display Name: {$fileSearchDocument->displayName}\n"; + echo "Create Time: {$fileSearchDocument->createTime}\n"; + echo "Update Time: {$fileSearchDocument->updateTime}\n"; + echo "--- \n"; +} +``` + +#### Delete File Search Document +Delete a file search document by name. + +```php +$client->fileSearchDocuments()->delete('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); +``` + ### Embedding Resource Embedding is a technique used to represent information as a list of floating point numbers in an array. With Gemini, you can represent text (words, sentences, and blocks of text) in a vectorized form, making it easier to compare and contrast embeddings. For example, two texts that share a similar subject matter or sentiment should have similar embeddings, which can be identified through mathematical comparison techniques such as cosine similarity. From 24e6f947bada3d72f447c2383129379f5a04dcf8 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:22:01 +0100 Subject: [PATCH 03/15] Update src/Resources/FileSearchStores.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Resources/FileSearchStores.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Resources/FileSearchStores.php b/src/Resources/FileSearchStores.php index 372bc4b..f0c416e 100644 --- a/src/Resources/FileSearchStores.php +++ b/src/Resources/FileSearchStores.php @@ -59,7 +59,7 @@ public function delete(string $name, bool $force = false): void public function upload(string $storeName, string $filename, ?MimeType $mimeType = null, ?string $displayName = null): UploadResponse { - $mimeType ??= MimeType::from((string) mime_content_type($filename)); + $mimeType ??= MimeType::from(mime_content_type($filename) ?: throw new \RuntimeException("Failed to determine MIME type for: {$filename}")); $displayName ??= $filename; /** @var ResponseDTO, done: bool, response?: array, error?: array }> $response */ From f44877c1c282deb0d2c1e885f35c9c69c1beaade Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:23:56 +0100 Subject: [PATCH 04/15] Update src/Responses/FileSearchStores/UploadResponse.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Responses/FileSearchStores/UploadResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Responses/FileSearchStores/UploadResponse.php b/src/Responses/FileSearchStores/UploadResponse.php index c94d34f..6307e61 100644 --- a/src/Responses/FileSearchStores/UploadResponse.php +++ b/src/Responses/FileSearchStores/UploadResponse.php @@ -15,7 +15,7 @@ class UploadResponse implements ResponseContract use Fakeable; /** - * @param array $metadata + * @param array|null $metadata * @param array|null $response * @param array|null $error */ From eb0fb541836c535e915b17310640fb14a921da61 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:24:08 +0100 Subject: [PATCH 05/15] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 38f9662..8270654 100644 --- a/README.md +++ b/README.md @@ -912,8 +912,6 @@ $response = $client->fileSearchStores()->list(pageSize: 10); foreach ($response->fileSearchStores as $fileSearchStore) { echo "Name: {$fileSearchStore->name}\n"; echo "Display Name: {$fileSearchStore->displayName}\n"; - echo "Create Time: {$fileSearchStore->createTime}\n"; - echo "Update Time: {$fileSearchStore->updateTime}\n"; echo "--- \n"; } ``` From 6429503a63210ebb63bebed050927c0b4ac46b0e Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:24:33 +0100 Subject: [PATCH 06/15] Update src/Requests/FileSearchStores/UploadRequest.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Requests/FileSearchStores/UploadRequest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Requests/FileSearchStores/UploadRequest.php b/src/Requests/FileSearchStores/UploadRequest.php index 21c21bf..ad66af7 100644 --- a/src/Requests/FileSearchStores/UploadRequest.php +++ b/src/Requests/FileSearchStores/UploadRequest.php @@ -44,6 +44,9 @@ public function toRequest(string $baseUrl, array $headers = [], array $queryPara $requestJson = json_encode($metadata); $contents = file_get_contents($this->filename); + if ($contents === false) { + throw new \RuntimeException("Failed to read file: {$this->filename}"); + } $request = $factory ->createRequest($this->method->value, str_replace('/v1', '/upload/v1', $baseUrl).$this->resolveEndpoint()) From 4645154883890ddfeaffa154a7a9d3b022b2c1e1 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:25:08 +0100 Subject: [PATCH 07/15] Update src/Requests/FileSearchStores/UploadRequest.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Requests/FileSearchStores/UploadRequest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Requests/FileSearchStores/UploadRequest.php b/src/Requests/FileSearchStores/UploadRequest.php index ad66af7..961c907 100644 --- a/src/Requests/FileSearchStores/UploadRequest.php +++ b/src/Requests/FileSearchStores/UploadRequest.php @@ -49,7 +49,10 @@ public function toRequest(string $baseUrl, array $headers = [], array $queryPara } $request = $factory - ->createRequest($this->method->value, str_replace('/v1', '/upload/v1', $baseUrl).$this->resolveEndpoint()) + ->createRequest( + $this->method->value, + preg_replace('#/v1(beta)?#', '/upload/v1$1', $baseUrl) . $this->resolveEndpoint() + ) ->withHeader('X-Goog-Upload-Protocol', 'multipart'); foreach ($headers as $name => $value) { $request = $request->withHeader($name, $value); From da3d1ea68b2d32daebe6c24bf71ec8e75a378526 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:25:27 +0100 Subject: [PATCH 08/15] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8270654..ebd1246 100644 --- a/README.md +++ b/README.md @@ -897,7 +897,7 @@ echo "File search store created: {$fileSearchStore->name}\n"; Get a specific file search store by name. ```php -$fileSearchStore = $client->fileSearchStores()->retrieve('fileSearchStores/my-search-store'); +$fileSearchStore = $client->fileSearchStores()->get('fileSearchStores/my-search-store'); echo "Name: {$fileSearchStore->name}\n"; echo "Display Name: {$fileSearchStore->displayName}\n"; From ce5456ee03269b36ccf019ad4e0e3be82da7d760 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:25:42 +0100 Subject: [PATCH 09/15] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ebd1246..fb7d116 100644 --- a/README.md +++ b/README.md @@ -976,7 +976,7 @@ echo "File search document created: {$fileSearchDocument->name}\n"; Get a specific file search document by name. ```php -$fileSearchDocument = $client->fileSearchDocuments()->retrieve('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); +$fileSearchDocument = $client->fileSearchStores()->getDocument('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); echo "Name: {$fileSearchDocument->name}\n"; echo "Display Name: {$fileSearchDocument->displayName}\n"; From a4e837cceafed36f39009b9610f547d01744a253 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:26:46 +0100 Subject: [PATCH 10/15] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index fb7d116..b3e1f0a 100644 --- a/README.md +++ b/README.md @@ -923,18 +923,6 @@ Delete a file search store by name. $client->fileSearchStores()->delete('fileSearchStores/my-search-store'); ``` -#### Update File Search Store -Update a file search store. - -```php -$fileSearchStore = $client->fileSearchStores()->update( - name: 'fileSearchStores/my-search-store', - displayName: 'My Updated Search Store', -); - -echo "File search store updated: {$fileSearchStore->name}\n"; -``` - ### File Search Documents #### Create File Search Document From b049f0b94f4312b258fd0426b994449163d3d82f Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:27:09 +0100 Subject: [PATCH 11/15] Update src/Contracts/Resources/FileSearchStoresContract.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Contracts/Resources/FileSearchStoresContract.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Contracts/Resources/FileSearchStoresContract.php b/src/Contracts/Resources/FileSearchStoresContract.php index c75a878..dc9ba8f 100644 --- a/src/Contracts/Resources/FileSearchStoresContract.php +++ b/src/Contracts/Resources/FileSearchStoresContract.php @@ -1,4 +1,5 @@ Date: Mon, 15 Dec 2025 11:27:48 +0100 Subject: [PATCH 12/15] Update src/Requests/FileSearchStores/DeleteRequest.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Requests/FileSearchStores/DeleteRequest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Requests/FileSearchStores/DeleteRequest.php b/src/Requests/FileSearchStores/DeleteRequest.php index 45a696c..01eab4c 100644 --- a/src/Requests/FileSearchStores/DeleteRequest.php +++ b/src/Requests/FileSearchStores/DeleteRequest.php @@ -24,6 +24,9 @@ public function resolveEndpoint(): string return $this->name; } + /** + * @return array + */ public function defaultQuery(): array { return $this->force ? ['force' => 'true'] : []; From f8d2ae98545f3346ad1c51138b6720ba9fd6faa8 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:28:01 +0100 Subject: [PATCH 13/15] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index b3e1f0a..8e4bb0c 100644 --- a/README.md +++ b/README.md @@ -879,15 +879,6 @@ if ($meta->state == FileState::Failed) { $fileSearchStore = $client->fileSearchStores()->create( displayName: 'My Search Store', - defaultSchema: new Schema( - declarations: [ - 'name' => new Schema(type: DataType::STRING), - 'size' => new Schema(type: DataType::INTEGER), - ], - ), - defaultDocumentConfig: [ - 'files/'.basename($meta->uri), - ], ); echo "File search store created: {$fileSearchStore->name}\n"; From bdb09516880a4c4c94d2b5cf22a4e55cc6f34d20 Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 11:28:14 +0100 Subject: [PATCH 14/15] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e4bb0c..527f7bd 100644 --- a/README.md +++ b/README.md @@ -980,7 +980,7 @@ foreach ($response->fileSearchDocuments as $fileSearchDocument) { Delete a file search document by name. ```php -$client->fileSearchDocuments()->delete('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); +$client->fileSearchStores()->deleteDocument('fileSearchStores/my-search-store/fileSearchDocuments/my-document'); ``` ### Embedding Resource From 195da56c8f1c02ceb843b74a6a410bb4bd13ddab Mon Sep 17 00:00:00 2001 From: Johann Fradj Date: Mon, 15 Dec 2025 12:05:30 +0100 Subject: [PATCH 15/15] fix: README --- README.md | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 527f7bd..25b6209 100644 --- a/README.md +++ b/README.md @@ -916,39 +916,20 @@ $client->fileSearchStores()->delete('fileSearchStores/my-search-store'); ### File Search Documents -#### Create File Search Document -Create a file search document within a store. +#### Upload File Search Document +Upload a local file directly to a file search store. ```php -use Gemini\Enums\FileState; use Gemini\Enums\MimeType; -$files = $client->files(); -echo "Uploading\n"; -$meta = $files->upload( +$response = $client->fileSearchStores()->upload( + storeName: 'fileSearchStores/my-search-store', filename: 'document2.pdf', mimeType: MimeType::APPLICATION_PDF, - displayName: 'Another document for search' -); -echo "Processing"; -do { - echo "."; - sleep(2); - $meta = $files->metadataGet($meta->uri); -} while (! $meta->state->complete()); -echo "\n"; - -if ($meta->state == FileState::Failed) { - die("Upload failed:\n".json_encode($meta->toArray(), JSON_PRETTY_PRINT)); -} - -$fileSearchDocument = $client->fileSearchDocuments()->create( - parent: 'fileSearchStores/my-search-store', - file: 'files/'.basename($meta->uri), - displayName: 'Another Search Document', + displayName: 'Another Search Document' ); -echo "File search document created: {$fileSearchDocument->name}\n"; +echo "File search document upload operation: {$response->name}\n"; ``` #### Get File Search Document @@ -965,9 +946,9 @@ echo "Display Name: {$fileSearchDocument->displayName}\n"; List all file search documents within a store. ```php -$response = $client->fileSearchDocuments()->list(parent: 'fileSearchStores/my-search-store', pageSize: 10); +$response = $client->fileSearchStores()->listDocuments(storeName: 'fileSearchStores/my-search-store', pageSize: 10); -foreach ($response->fileSearchDocuments as $fileSearchDocument) { +foreach ($response->documents as $fileSearchDocument) { echo "Name: {$fileSearchDocument->name}\n"; echo "Display Name: {$fileSearchDocument->displayName}\n"; echo "Create Time: {$fileSearchDocument->createTime}\n";