Skip to content
wiki.fftac.org

Promptrunner - Source Excerpt 01

Back to Promptrunner

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiralist-workspace/includes/Services/PromptRunner.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Services/PromptRunner.php

<?php

namespace SpiralistWorkspace\Services;

use SpiralistWorkspace\Auth\Permissions;
use SpiralistWorkspace\Config\Settings;
use SpiralistWorkspace\Domain\AuditRepository;
use SpiralistWorkspace\Domain\ConversationRepository;
use SpiralistWorkspace\Domain\PromptRepository;
use SpiralistWorkspace\Domain\RunRepository;
use WP_Error;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Runs prompts and manages prompt-based conversations.
 */
class PromptRunner
{
    private PromptRepository $prompts;
    private Permissions $permissions;
    private RunRepository $runs;
    private ConversationRepository $conversations;
    private AuditRepository $audit;
    private PlaceholderService $placeholders;
    private RateLimiter $rate_limiter;
    private Settings $settings;

    public function __construct(
        PromptRepository $prompts,
        Permissions $permissions,
        RunRepository $runs,
        ConversationRepository $conversations,
        AuditRepository $audit,
        PlaceholderService $placeholders,
        RateLimiter $rate_limiter,
        Settings $settings
    ) {
        $this->prompts = $prompts;
        $this->permissions = $permissions;
        $this->runs = $runs;
        $this->conversations = $conversations;
        $this->audit = $audit;
        $this->placeholders = $placeholders;
        $this->rate_limiter = $rate_limiter;
        $this->settings = $settings;
    }

    /**
     * Renders a prompt packet in single-run mode.
     *
     * @return array|WP_Error
     */
    public function run_prompt(int $prompt_id, int $user_id, array $variables = [], string $input_text = '')
    {
        $prompt = $this->prompts->get($prompt_id, $user_id);
        if (empty($prompt) || !$this->permissions->can_run_prompt($prompt, $user_id)) {
            return new WP_Error('spiralist_workspace_prompt_forbidden', 'Prompt not found.', ['status' => 404]);
        }

        if (!$this->rate_limiter->assert_can_run($user_id)) {
            return new WP_Error('spiralist_workspace_rate_limited', 'Daily run limit reached.', ['status' => 429]);
        }

        if (!empty($prompt['system_steps']) && is_array($prompt['system_steps'])) {
            return $this->run_prompt_system($prompt, $user_id, $variables, $input_text);
        }

        $payload = $this->build_request_payload($prompt, $variables, $input_text, null, false);
        $run_id = $this->runs->create(
            [
                'prompt_post_id' => $prompt_id,
                'user_id' => $user_id,
                'version_number_used' => (int) $prompt['version_number'],
                'model_used' => (string) $payload['model'],
                'request_payload_json' => $payload,
                'input_text' => $input_text,
                'status' => 'running',
            ]
        );

        $output_text = $this->build_portable_prompt_packet($prompt, $payload, $variables, $input_text);
        $usage = $this->local_packet_usage($output_text);

        $this->runs->complete(
            $run_id,
            [
                'response_payload_json' => [
                    'mode' => 'prompt_art_only',
                    'network' => 'none',
                    'packet_type' => 'single_prompt',
                ],
                'output_text' => $output_text,
                'status' => 'completed',
                'token_usage_json' => $usage,
                'error_message' => '',
            ]
        );

        $this->audit->log($user_id ?: null, 'prompt_packet_rendered', 'prompt_run', $run_id, ['prompt_id' => $prompt_id]);

        return $this->runs->get($run_id);
    }

    /**
     * Executes a prompt system by chaining saved prompt modules.
     *
     * @return array|WP_Error
     */
    private function run_prompt_system(array $system_prompt, int $user_id, array $variables, string $input_text)
    {
        $steps = array_slice((array) ($system_prompt['system_steps'] ?? []), 0, 12);
        if (empty($steps)) {
            return new WP_Error('spiralist_workspace_empty_system', 'Prompt system has no runnable steps.', ['status' => 400]);
        }

        $run_id = $this->runs->create(
            [
                'prompt_post_id' => (int) ($system_prompt['id'] ?? 0),
                'user_id' => $user_id,
                'version_number_used' => (int) ($system_prompt['version_number'] ?? 1),
                'model_used' => 'prompt-system',
                'request_payload_json' => [
                    'type' => 'prompt_system',
                    'steps' => $steps,
                    'input_text' => $input_text,
                ],
                'input_text' => $input_text,
                'status' => 'running',
            ]
        );

        $current_input = $input_text;
        $step_results = [];
        $usage = [];

        foreach ($steps as $index => $step) {
            $step_prompt_id = (int) ($step['prompt_id'] ?? 0);
            if ($step_prompt_id <= 0 || $step_prompt_id === (int) ($system_prompt['id'] ?? 0)) {
                $this->runs->complete(
                    $run_id,
                    [
                        'response_payload_json' => ['steps' => $step_results],
                        'output_text' => $current_input,
                        'status' => 'failed',
                        'token_usage_json' => $usage,
                        'error_message' => 'Prompt system contains an invalid or recursive step.',
                    ]
                );

                return new WP_Error('spiralist_workspace_invalid_system_step', 'Prompt system contains an invalid or recursive step.', ['status' => 400]);
            }

            $step_prompt = $this->prompts->get($step_prompt_id, $user_id);
            if (empty($step_prompt) || !$this->permissions->can_run_prompt($step_prompt, $user_id)) {
                $this->runs->complete(
                    $run_id,
                    [
                        'response_payload_json' => ['steps' => $step_results],
                        'output_text' => $current_input,
                        'status' => 'failed',
                        'token_usage_json' => $usage,
                        'error_message' => 'A prompt system step is not available.',
                    ]
                );

                return new WP_Error('spiralist_workspace_system_step_forbidden', 'A prompt system step is not available.', ['status' => 404]);
            }

            $payload = $this->build_request_payload($step_prompt, $variables, $current_input, null, false);
            $payload['metadata']['system_prompt_id'] = (string) ($system_prompt['id'] ?? '');
            $payload['metadata']['system_step_index'] = (string) ($index + 1);

            $output_text = $this->build_portable_prompt_packet(
                $step_prompt,
                $payload,
                $variables,
                $current_input,
                'Prompt System Step ' . (string) ($index + 1)
            );
            $step_usage = $this->local_packet_usage($output_text);
            $usage[] = [
                'step' => $index + 1,
                'prompt_id' => $step_prompt_id,
                'usage' => $step_usage,
            ];
            $step_results[] = [
                'step' => $index + 1,
                'prompt_id' => $step_prompt_id,
                'title' => (string) ($step_prompt['title'] ?? ''),
                'version_number_used' => (int) ($step_prompt['version_number'] ?? 1),
                'model_used' => (string) ($payload['model'] ?? ''),
                'status' => 'completed',
                'input_text' => $current_input,
                'output_text' => $output_text,
                'token_usage_json' => $step_usage,
            ];
            $current_input = $output_text;
        }

        $current_input = $this->build_prompt_system_packet($system_prompt, $step_results, $input_text);

        $this->runs->complete(
            $run_id,
            [
                'response_payload_json' => [
                    'mode' => 'prompt_art_only',
                    'network' => 'none',
                    'packet_type' => 'prompt_system',
                    'steps' => $step_results,
                ],
                'output_text' => $current_input,
                'status' => 'completed',
                'token_usage_json' => ['steps' => $usage],
                'error_message' => '',
            ]
        );