*/ public const DEFAULTS = [ 'enabled' => true, // Site switch: off stops every new run. MAZE_GRAPH_DISABLED forces it off. 'default_model' => '', // "provider:model"; empty lets WordPress pick the first capable model. 'site_instructions' => '', // Added to every session's system prompt (max 4,000 characters). 'tools_enabled' => true, // Offer the site's read-only abilities to the model. 'tool_overrides' => [], // Ability name => bool; unset follows the default (core/* and maze-graph/* on). 'assistant_all_enabled_tools' => false, // Let only the built-in main assistant use all enabled tools, subject to policy. 'allowed_roles' => ['administrator'], // Roles that may chat; administrators always may (Support\Access). 'max_steps' => 8, // Model calls per reply, tool rounds included. 'max_output_tokens' => 0, // 0 leaves the model's own limit. 'daily_token_cap' => 500000, // Site-wide tokens per day; 0 is no cap. 'retention_days' => 0, // 0 keeps sessions until deleted. 'log_retention_days' => 7, // Operational event logs expire after a week by default; max 365 days. 'delete_data_on_uninstall' => false, 'runs_per_hour' => 60, // Replies one person may start per hour; 0 is no limit. 'context_strategy' => 'window', // History handling: window, clear (old tool results) or compact (summaries). 'mcp_share_read_tools' => false, // Mark MazeGraph's read-only tools public for WordPress's MCP Adapter (decision 038). 'modules' => [], // Module id => bool; unset follows the module's default (decision 043). 'debug_timing' => false, // Collect client-side request timings for troubleshooting. ]; public const INSTRUCTIONS_LIMIT = 4000; public function __construct(protected SettingsRepository $store) { } /** * One setting, cast to the type of its default. * * @since 0.1.0 */ public function get(string $key): mixed { if (!array_key_exists($key, self::DEFAULTS)) { throw new \InvalidArgumentException(esc_html("Unknown MazeGraph setting \"{$key}\".")); } return $this->cast($key, $this->store->get($key, self::DEFAULTS[$key])); } /** * Every setting. * * @since 0.1.0 * @return array */ public function all(): array { $values = []; foreach (array_keys(self::DEFAULTS) as $key) { $values[$key] = $this->get($key); } return $values; } /** * Save known settings; unknown keys are ignored. * * @since 0.1.0 * @param array $values */ public function update(array $values): void { foreach ($values as $key => $value) { if (array_key_exists($key, self::DEFAULTS)) { $this->store->set($key, $this->cast($key, $value)); } } } public function bool(string $key): bool { return (bool) $this->get($key); } public function int(string $key): int { return (int) $this->get($key); } public function string(string $key): string { return (string) $this->get($key); } /** * Whether new runs may start: the setting, unless the `MAZE_GRAPH_DISABLED` constant forces the plugin off. * * @since 0.1.0 */ public function runsAllowed(): bool { return !(defined('MAZE_GRAPH_DISABLED') && MAZE_GRAPH_DISABLED) && $this->bool('enabled'); } protected function cast(string $key, mixed $value): mixed { return match (gettype(self::DEFAULTS[$key])) { 'boolean' => filter_var($value, FILTER_VALIDATE_BOOLEAN), 'integer' => max(0, (int) $value), 'array' => match ($key) { 'tool_overrides' => $this->castOverrides($value), 'modules' => $this->castOverrides($value, '#^[a-z0-9-]+(/[a-z0-9-]+)?$#'), default => $this->castList($value), }, default => is_scalar($value) ? (string) $value : '', }; } /** * A list of slugs (role names). * * @return list */ protected function castList(mixed $value): array { if (is_string($value)) { $value = json_decode($value, true); } return array_values(array_unique(array_filter(array_map( static fn ($item): string => is_string($item) ? sanitize_key($item) : '', is_array($value) ? $value : [] )))); } /** * @return array */ protected function castOverrides(mixed $value, string $pattern = '#^[a-z0-9-]+/[a-z0-9-]+$#'): array { if (is_string($value)) { $value = json_decode($value, true); } $clean = []; foreach (is_array($value) ? $value : [] as $name => $on) { if (is_string($name) && preg_match($pattern, $name)) { $clean[$name] = filter_var($on, FILTER_VALIDATE_BOOLEAN); } } return $clean; } }