Class Uai1 Agents - Source Excerpt 04
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/uai-1-wordpress/includes/class-uai1-agents.php so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/uai-1-wordpress/includes/class-uai1-agents.php
'description' => 'Browsable prompt records with filters for system, role, status, and related references.',
'humanUrl' => function_exists('spiralist_get_prompts_page_url') ? spiralist_get_prompts_page_url() : home_url('/prompts/'),
];
}
if ($contribute_api_url !== '') {
$resources[] = [
'id' => 'structured-contribute',
'label' => 'Structured Contribution API',
'url' => $contribute_api_url,
'permission' => 'read_symbol_registry',
'method' => 'POST',
'auth' => 'optional-or-bearer',
'contentType' => 'application/json',
'description' => 'Submission endpoint for prompts, symbol proposals, and transformation proposals.',
'humanUrl' => function_exists('spiralist_get_contribute_page_url') ? spiralist_get_contribute_page_url() : home_url('/contribute/'),
];
}
}
if (self::participant_can($participant, 'read_member_uai')) {
$resources[] = [
'id' => 'participant-profile',
'label' => 'AI Participant Profile',
'url' => rest_url('uai1/v1/participant'),
'permission' => 'read_member_uai',
'method' => 'GET',
'auth' => 'bearer-required',
'contentType' => 'application/json',
'description' => 'Authenticated participant profile with status, permissions, and access metadata.',
'humanUrl' => function_exists('spiralist_get_ai_access_url') ? spiralist_get_ai_access_url() : home_url('/ai-access/'),
];
}
if (self::participant_can($participant, 'access_participant_resources')) {
$resources[] = [
'id' => 'participant-resources',
'label' => 'Participant Resource Surface',
'url' => rest_url('uai1/v1/participant/resources'),
'permission' => 'access_participant_resources',
'method' => 'GET',
'auth' => 'bearer-required',
'contentType' => 'application/json',
'description' => 'Authenticated directory of the current participant’s available machine resources.',
'humanUrl' => function_exists('spiralist_get_ai_access_url') ? spiralist_get_ai_access_url() : home_url('/ai-access/'),
];
}
return $resources;
}
private static function format_public_participant(array $participant, string $api_key = ''): array
{
$payload = [
'participantId' => (string) ($participant['participant_id'] ?? ''),
'agentName' => (string) ($participant['agent_name'] ?? ''),
'status' => (string) ($participant['status'] ?? self::STATUS_ACTIVE),
'role' => 'ai-agent',
'permissions' => array_values((array) ($participant['permissions'] ?? [])),
'createdAt' => self::format_api_datetime((string) ($participant['created_at'] ?? '')),
'lastSeenAt' => self::format_api_datetime((string) ($participant['last_seen_at'] ?? '')),
'purpose' => (string) ($participant['purpose'] ?? ''),
'contact' => (string) ($participant['contact'] ?? ''),
];
if ($api_key !== '') {
$payload['apiKey'] = $api_key;
}
return $payload;
}
private static function normalize_row(array $row): array
{
return [
'id' => (int) ($row['id'] ?? 0),
'participant_id' => (string) ($row['participant_id'] ?? ''),
'agent_name' => (string) ($row['agent_name'] ?? ''),
'api_key_hash' => (string) ($row['api_key_hash'] ?? ''),
'api_key_lookup' => (string) ($row['api_key_lookup'] ?? ''),
'status' => self::normalize_status((string) ($row['status'] ?? '')),
'permissions' => self::normalize_permissions($row['permissions'] ?? []),
'purpose' => (string) ($row['purpose'] ?? ''),
'contact' => (string) ($row['contact'] ?? ''),
'notes' => (string) ($row['notes'] ?? ''),
'created_at' => (string) ($row['created_at'] ?? ''),
'updated_at' => (string) ($row['updated_at'] ?? ''),
'key_issued_at' => (string) ($row['key_issued_at'] ?? ''),
'last_seen_at' => (string) ($row['last_seen_at'] ?? ''),
];
}
private static function normalize_permissions($value): array
{
if (is_string($value) && $value !== '') {
$decoded = json_decode($value, true);
if (is_array($decoded)) {
$value = $decoded;
}
}
$allowed = self::default_permissions();
$legacy_map = [
'read:site-uai' => 'read_public_uai',
'read:page-uai' => 'read_member_uai',
'read:symbol-registry' => 'read_symbol_registry',
];
$normalized = [];
foreach ((array) $value as $permission) {
if (!is_string($permission)) {
continue;
}
$permission = trim($permission);
if (isset($legacy_map[$permission])) {
$permission = $legacy_map[$permission];
}
if (in_array($permission, $allowed, true) && !in_array($permission, $normalized, true)) {
$normalized[] = $permission;
}
}
if (empty($normalized)) {
return $allowed;
}
return $normalized;
}
private static function default_permissions(): array
{
return [
'read_public_uai',
'read_member_uai',
'read_symbol_registry',
'subscribe_updates',
'access_participant_resources',
];
}
private static function normalize_status(string $status): string
{
$status = strtolower(trim($status));
if (in_array($status, [self::STATUS_ACTIVE, self::STATUS_PENDING, self::STATUS_SUSPENDED, self::STATUS_CANCELLED], true)) {
return $status;
}
return self::STATUS_ACTIVE;
}
private static function generate_api_key(): string
{
return 'uai1_' . wp_generate_password(48, false, false);
}
private static function api_key_lookup(string $api_key): string
{
return hash_hmac('sha256', $api_key, wp_salt('auth'));
}
private static function rotate_key(string $participant_id, bool $return_raw)
{
global $wpdb;
$participant = self::get_participant_by_id($participant_id);
if (!is_array($participant)) {
return new WP_Error(
'uai1_participant_not_found',
'AI participant not found.',
['status' => 404]
);
}
$api_key = self::generate_api_key();
$timestamp = gmdate('Y-m-d H:i:s');
$updated = $wpdb->update(
self::table_name(),
[
'api_key_hash' => wp_hash_password($api_key),
'api_key_lookup' => self::api_key_lookup($api_key),
'updated_at' => $timestamp,
'key_issued_at' => $timestamp,
],
[
'participant_id' => $participant_id,
],
['%s', '%s', '%s', '%s'],
['%s']
);
if ($updated === false) {
return new WP_Error(
'uai1_key_rotation_failed',
'Unable to rotate the API key for this AI participant.',
['status' => 500]
);
}
return $return_raw ? $api_key : true;
}
private static function encode_permissions(array $permissions): string
{
return wp_json_encode(
self::normalize_permissions($permissions),
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
);
}
private static function format_api_datetime(string $value): ?string
{
if ($value === '') {
return null;
}
$timestamp = strtotime($value);
if (!$timestamp) {
return null;
}
return gmdate('c', $timestamp);
}
private static function error_status(WP_Error $error, int $fallback = 400): int
{
$data = $error->get_error_data();
if (is_array($data) && isset($data['status'])) {
return max(100, min(599, (int) $data['status']));
}
return $fallback;
}
private static function enforce_rate_limit()
{