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
42 changes: 36 additions & 6 deletions src/Data/GoogleSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,56 @@

namespace Gemini\Data;

use InvalidArgumentException;
use stdClass;

/**
* This type has no fields.
* GoogleSearch tool type. Tool to support Google Search in Model. Powered by Google.
*
* https://ai.google.dev/api/caching#GoogleSearch
*/
final class GoogleSearch
{
/**
* @param Interval|null $timeRangeFilter Optional. Filter search results to a specific time range. If customers set a start time, they must set an end time (and vice versa).
*
* @throws InvalidArgumentException When timeRangeFilter has only startTime or only endTime specified
*/
public function __construct(
) {}
public readonly ?Interval $timeRangeFilter = null,
) {
if ($this->timeRangeFilter !== null) {
$hasStartTime = $this->timeRangeFilter->startTime !== null;
$hasEndTime = $this->timeRangeFilter->endTime !== null;

public static function from(): self
// GoogleSearch requires both start and end time to be set together
if ($hasStartTime !== $hasEndTime) {
throw new InvalidArgumentException('In GoogleSearch timeRangeFilter, if you set a start time, you must set an end time (and vice versa)');
}
}
}

/**
* @param array{timeRangeFilter?: array{startTime?: ?string, endTime?: ?string}} $attributes
*/
public static function from(array $attributes = []): self
{
return new self;
return new self(
timeRangeFilter: isset($attributes['timeRangeFilter']) ? Interval::from($attributes['timeRangeFilter']) : null,
);
}

public function toArray(): stdClass
/**
* @return stdClass|array{timeRangeFilter: array{startTime?: ?string, endTime?: ?string}|stdClass}
*/
public function toArray(): stdClass|array
{
return new stdClass;
if ($this->timeRangeFilter === null) {
return new stdClass;
}

return [
'timeRangeFilter' => $this->timeRangeFilter->toArray(),
];
}
}
102 changes: 102 additions & 0 deletions src/Data/Interval.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Gemini\Data;

use DateTime;
use InvalidArgumentException;
use stdClass;

/**
* Represents a time interval, encoded as a Timestamp start (inclusive) and a Timestamp end (exclusive).
*
* The start must be less than or equal to the end. When the start equals the end, the interval is empty (matches no time). When both start and end are unspecified, the interval matches any time.
*
* https://ai.google.dev/api/caching#Interval
*/
final class Interval
{
/**
* @param ?string $startTime Optional. Inclusive start of the interval. If specified, a Timestamp matching this interval will have to be the same or after the start. Uses RFC 3339 format. Examples: "2014-10-02T15:01:23Z", "2014-10-02T15:01:23.045123456Z" or "2014-10-02T15:01:23+05:30".
* @param ?string $endTime Optional. Exclusive end of the interval. If specified, a Timestamp matching this interval will have to be before the end. Uses RFC 3339 format. Examples: "2014-10-02T15:01:23Z", "2014-10-02T15:01:23.045123456Z" or "2014-10-02T15:01:23+05:30".
*
* @throws InvalidArgumentException When timestamp format is invalid or start time is after end time
*/
public function __construct(
public readonly ?string $startTime,
public readonly ?string $endTime,
) {
if ($this->startTime !== null && ! $this->isValidTimestamp($this->startTime)) {
throw new InvalidArgumentException('startTime must be in RFC 3339 timestamp format');
}

if ($this->endTime !== null && ! $this->isValidTimestamp($this->endTime)) {
throw new InvalidArgumentException('endTime must be in RFC 3339 timestamp format');
}

if ($this->startTime !== null && $this->endTime !== null) {
$startTimestamp = strtotime($this->startTime);
$endTimestamp = strtotime($this->endTime);

if ($startTimestamp === false || $endTimestamp === false) {
throw new InvalidArgumentException('Invalid timestamp format');
}

if ($startTimestamp > $endTimestamp) {
throw new InvalidArgumentException('startTime must be less than or equal to endTime');
}
}
}

/**
* @param array{startTime?: ?string, endTime?: ?string} $attributes
*/
public static function from(array $attributes): self
{
return new self($attributes['startTime'] ?? null, $attributes['endTime'] ?? null);
}

/**
* @return stdClass|array{startTime?: ?string, endTime?: ?string}
*/
public function toArray(): stdClass|array
{
$data = [];

if ($this->startTime !== null) {
$data['startTime'] = $this->startTime;
}

if ($this->endTime !== null) {
$data['endTime'] = $this->endTime;
}

return empty($data) ? new stdClass : $data;
}

/**
* Validates if a string is in RFC 3339 timestamp format
*/
private function isValidTimestamp(string $timestamp): bool
{
// RFC 3339 regex pattern
$pattern = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$/';

if (! preg_match($pattern, $timestamp)) {
return false;
}

// Handle Z timezone suffix by converting to +00:00
$normalizedTimestamp = str_replace('Z', '+00:00', $timestamp);

// Try with extended format first (includes microseconds)
$dateTime = DateTime::createFromFormat(DateTime::RFC3339_EXTENDED, $normalizedTimestamp);
if ($dateTime === false) {
// Try with standard RFC3339 format
$dateTime = DateTime::createFromFormat(DateTime::RFC3339, $normalizedTimestamp);
}

return $dateTime !== false;
}
}