diff --git a/src/Usage.php b/src/Usage.php index 74b7230..27745c7 100644 --- a/src/Usage.php +++ b/src/Usage.php @@ -23,6 +23,11 @@ class Usage */ public $character; + /** + * @var UsageDetail|null Usage details for characters used by this API key only. + */ + public $apiKeyCharacter; + /** * @var UsageDetail|null Usage details for documents. */ @@ -33,22 +38,67 @@ class Usage */ public $teamDocument; + /** + * @var UsageDetail|null Usage details for speech-to-text, in minutes. + */ + public $speechToTextMinutes; + + /** + * @var UsageDetail|null Usage details for speech-to-text, in milliseconds. + */ + public $speechToTextMilliseconds; + + /** + * @var UsageDetail|null Usage details for speech-to-speech, in minutes. + */ + public $speechToSpeechMinutes; + + /** + * @var UsageProduct[] Usage broken down per product, empty if the account does not report it. + */ + public $products; + + /** + * @var string|null Start of the billing period, as an ISO 8601 timestamp. + */ + public $startTime; + + /** + * @var string|null End of the billing period, as an ISO 8601 timestamp. + */ + public $endTime; + /** * @return bool True if any usage type limit has been reached or passed, otherwise false. */ public function anyLimitReached(): bool { - return ($this->character !== null && $this->character->limitReached()) || - ($this->document !== null && $this->document->limitReached()) || - ($this->teamDocument !== null && $this->teamDocument->limitReached()); + $details = [ + $this->apiKeyCharacter, + $this->character, + $this->document, + $this->teamDocument, + $this->speechToTextMinutes, + $this->speechToSpeechMinutes, + ]; + /** @var UsageDetail|null $detail */ + foreach ($details as $detail) { + if ($detail !== null && $detail->limitReached()) { + return true; + } + } + return false; } public function __toString(): string { $list = [ + 'API key characters' => $this->apiKeyCharacter, 'Characters' => $this->character, 'Documents' => $this->document, 'Team documents' => $this->teamDocument, + 'Speech-to-text minutes' => $this->speechToTextMinutes, + 'Speech-to-speech minutes' => $this->speechToSpeechMinutes, ]; $result = 'Usage this billing period:'; foreach ($list as $label => $detail) { @@ -71,8 +121,20 @@ public function __construct(string $content) } $this->character = $this->buildUsageDetail('character', $json); + $this->apiKeyCharacter = $this->buildUsageDetail('api_key_character', $json); $this->document = $this->buildUsageDetail('document', $json); $this->teamDocument = $this->buildUsageDetail('team_document', $json); + $this->speechToTextMinutes = $this->buildUsageDetail('speech_to_text_minutes', $json); + $this->speechToTextMilliseconds = $this->buildUsageDetail('speech_to_text_milliseconds', $json); + $this->speechToSpeechMinutes = $this->buildUsageDetail('speech_to_speech_minutes', $json); + $this->products = array_map( + function (array $product): UsageProduct { + return new UsageProduct($product); + }, + $json['products'] ?? [] + ); + $this->startTime = $json['start_time'] ?? null; + $this->endTime = $json['end_time'] ?? null; } private function buildUsageDetail(string $prefix, array $json): ?UsageDetail diff --git a/src/UsageProduct.php b/src/UsageProduct.php new file mode 100644 index 0000000..39ac3b3 --- /dev/null +++ b/src/UsageProduct.php @@ -0,0 +1,53 @@ +productType = $json['product_type'] ?? ''; + $this->billingUnit = $json['billing_unit'] ?? ''; + $this->apiKeyUnitCount = $json['api_key_unit_count'] ?? 0; + $this->accountUnitCount = $json['account_unit_count'] ?? 0; + $this->apiKeyCharacterCount = $json['api_key_character_count'] ?? 0; + $this->characterCount = $json['character_count'] ?? 0; + } +} diff --git a/tests/UsageTest.php b/tests/UsageTest.php new file mode 100644 index 0000000..3442c61 --- /dev/null +++ b/tests/UsageTest.php @@ -0,0 +1,95 @@ +assertEquals(636, $usage->apiKeyCharacter->count); + $this->assertEquals(30, $usage->speechToTextMinutes->count); + $this->assertEquals(600, $usage->speechToSpeechMinutes->limit); + $this->assertEquals(0, $usage->speechToTextMilliseconds->limit); + $this->assertNull($usage->document); + $this->assertEquals('2025-05-13T09:18:42Z', $usage->startTime); + $this->assertEquals('speechToText', $usage->products[1]->productType); + $this->assertEquals('minutes', $usage->products[1]->billingUnit); + $this->assertEquals(30, $usage->products[1]->apiKeyUnitCount); + // Milliseconds are reported with a limit of 0, they must not count as a reached limit. + $this->assertFalse($usage->anyLimitReached()); + $this->assertStringContainsString('Speech-to-text minutes: 30 of 600', strval($usage)); + } + + public function testSpeechLimitReached() + { + $json = str_replace('"speech_to_speech_minutes_count": 12', '"speech_to_speech_minutes_count": 600', self::PRO_RESPONSE); + $this->assertNotEquals($json, self::PRO_RESPONSE); + $this->assertTrue((new Usage($json))->anyLimitReached()); + } + + public function testApiKeyAndAccountLimitReached() + { + $usage = new Usage( + '{"character_count":500001,"character_limit":500000,"api_key_character_count":500001,"api_key_character_limit":500000}' + ); + + $this->assertTrue($usage->character->limitReached()); + $this->assertTrue($usage->apiKeyCharacter->limitReached()); + $this->assertTrue($usage->anyLimitReached()); + } + + public function testFreeAccountResponseUnchanged() + { + $usage = new Usage('{"character_count":180,"character_limit":500000}'); + + $this->assertEquals(180, $usage->character->count); + $this->assertNull($usage->apiKeyCharacter); + $this->assertNull($usage->speechToTextMinutes); + $this->assertSame([], $usage->products); + $this->assertNull($usage->startTime); + $this->assertFalse($usage->anyLimitReached()); + } +}