> */ private array $entries; /** @var list> */ private array $overrides; private string $version; private string $verified; private string $notice; /** * @param array $dataset the bundled list (see prices.php) * @param list> $overrides validated administrator rates (PriceOverrides::all()) */ public function __construct(array $dataset, array $overrides = []) { $this->entries = array_values((array) ($dataset['entries'] ?? [])); $this->overrides = array_values($overrides); $this->version = (string) ($dataset['version'] ?? ''); $this->verified = (string) ($dataset['verified'] ?? ''); $this->notice = (string) ($dataset['notice'] ?? ''); } /** @param list> $overrides */ public static function bundled(array $overrides = []): self { /** @var array $data */ $data = require __DIR__ . '/prices.php'; return new self($data, $overrides); } public function version(): string { return $this->version; } public function notice(): string { return $this->notice; } public function verifiedOn(): string { return $this->verified; } /** True when the bundled list was last verified more than $days days before $now: it is shown with a warning, never trusted as current. */ public function isStale(int $now, int $days = 120): bool { $at = strtotime($this->verified . ' UTC'); return $at === false || $now > $at + $days * 86400; } /** @return list> */ public function entries(): array { return $this->entries; } /** @return list the dates (UTC midnight) on which some bundled price list starts */ public function boundaries(): array { $b = []; foreach ($this->entries as $e) { if ((string) ($e['status'] ?? '') !== 'verified') { continue; } $t = strtotime((string) $e['valid_from'] . ' UTC'); if ($t !== false) { $b[$t] = $t; } } sort($b); return array_values($b); } /** @return list the prompt sizes at which a bundled price changes tier */ public function tierCaps(): array { $c = []; foreach ($this->entries as $e) { if ((string) ($e['status'] ?? '') !== 'verified') { continue; } foreach ((array) $e['tiers'] as $t) { if (isset($t['up_to'])) { $c[(int) $t['up_to']] = (int) $t['up_to']; } } } sort($c); return array_values($c); } /** @return Rate|string the rate, or self::NO_RATE / self::BEFORE_RATES */ public function rateFor(string $provider, string $model, int $inputTokens, int $at): Rate|string { $model = strtolower($model); if ($model === '') { return self::NO_RATE; } foreach ($this->overrides as $o) { if (strtolower((string) $o['provider']) === $provider && self::matches(strtolower((string) $o['model']), $model, true)) { return $this->overrideRate($o); } } $exact = []; $family = []; foreach ($this->entries as $e) { if ((string) $e['provider'] !== $provider || (string) ($e['status'] ?? '') !== 'verified') { continue; // an entry that was not verified against the provider's own page is never used } $id = strtolower((string) $e['model']); if ($id === $model) { $exact[] = $e; } elseif (in_array($model, array_map('strtolower', (array) ($e['snapshots'] ?? [])), true)) { $family[] = $e; // a dated name the provider's own model page lists under this model } } $candidates = $exact !== [] ? $exact : $family; if ($candidates === []) { return self::NO_RATE; } $chosen = null; $chosenFrom = -1; foreach ($candidates as $e) { $from = (int) strtotime((string) $e['valid_from'] . ' UTC'); if ($from <= $at && $from > $chosenFrom) { $chosen = $e; $chosenFrom = $from; } } if ($chosen === null) { return self::BEFORE_RATES; } foreach ((array) $chosen['tiers'] as $t) { if (!isset($t['up_to']) || $inputTokens <= (int) $t['up_to']) { return new Rate( $provider, (string) $chosen['model'], (string) $chosen['currency'], (int) Decimal::toMicro((string) $t['input']), isset($t['cached_input']) ? Decimal::toMicro((string) $t['cached_input']) : null, (int) Decimal::toMicro((string) $t['output']), isset($t['up_to']) ? (int) $t['up_to'] : null, $chosenFrom, (string) $chosen['verified'], (string) $chosen['source'], Rate::BUNDLED ); } } return self::NO_RATE; } private static function matches(string $id, string $model, bool $family): bool { return $model === $id || ($family && preg_match('/^' . preg_quote($id, '/') . '-' . self::SNAPSHOT . '$/D', $model) === 1); } /** @param array $o */ private function overrideRate(array $o): Rate { return new Rate( (string) $o['provider'], (string) $o['model'], (string) $o['currency'], (int) Decimal::toMicro((string) $o['input']), isset($o['cached_input']) ? Decimal::toMicro((string) $o['cached_input']) : null, (int) Decimal::toMicro((string) $o['output']), null, 0, gmdate('Y-m-d', (int) $o['entered_at']), 'entered by an administrator', Rate::OVERRIDE ); } }