get_context( $session_id, $post_id ) : array(); $session_context = $saved_context['context'] ?? array(); $messages = $saved_context['messages'] ?? array(); if ( empty( $messages ) ) { $messages = $this->get_request_messages( $request_params ); } $token_policy = $this->get_token_policy( $session_context ); $recent_messages = $this->prepare_recent_messages( $messages, $token_policy['max_recent_messages'] ); $recent_messages = $this->remove_active_user_message( $recent_messages, $request_params['latestUserMessage'] ?? '' ); $post_config = $this->resolve_post_config( $saved_context, $request_params ); $plan = $this->resolve_plan( $saved_context, $request_params, $post_id ); $working_context = $this->build_working_context( $task, $session_context, $recent_messages, $plan, $post_config, $request_params ); return array( 'system_context' => '', 'working_context' => $working_context, 'active_content' => $this->get_active_content( $request_params ), 'research_context' => $this->build_research_context( $session_context, $request_params, $token_policy['max_research_snippets'] ), 'audit' => array( 'included_recent_messages' => count( $recent_messages ), 'included_research_items' => $this->count_research_items( $session_context, $request_params, $token_policy['max_research_snippets'] ), 'estimated_input_tokens' => $this->estimate_tokens( $working_context ), 'used_full_history' => false, ), ); } /** * Build a system message that can be inserted after the primary system prompt. * * @param string $task Task name. * @param string $session_id Session ID. * @param int $post_id Post ID. * @param array $request_params Request params. * @return array Context system message and audit metadata. */ public function build_system_message( $task, $session_id, $post_id, $request_params = array() ) { $package = $this->build_for_task( $task, $session_id, $post_id, $request_params ); $content = trim( $package['working_context'] . "\n" . $package['active_content'] . "\n" . $package['research_context'] ); if ( '' === $content ) { return array( 'message' => null, 'audit' => $package['audit'], ); } return array( 'message' => array( 'role' => 'system', 'content' => $content, ), 'audit' => $package['audit'], ); } /** * Get task token policy. * * @param array $session_context Session context. * @return array Token policy. */ private function get_token_policy( $session_context ) { $policy = isset( $session_context['token_policy'] ) && is_array( $session_context['token_policy'] ) ? $session_context['token_policy'] : array(); return array( 'max_recent_messages' => max( 2, (int) ( $policy['max_recent_messages'] ?? 6 ) ), 'max_summary_tokens' => max( 200, (int) ( $policy['max_summary_tokens'] ?? 600 ) ), 'max_research_snippets' => max( 0, (int) ( $policy['max_research_snippets'] ?? 5 ) ), ); } /** * Get messages from request fallback. * * @param array $request_params Request params. * @return array Messages. */ private function get_request_messages( $request_params ) { if ( ! empty( $request_params['messages'] ) && is_array( $request_params['messages'] ) ) { return $request_params['messages']; } if ( ! empty( $request_params['chatHistory'] ) && is_array( $request_params['chatHistory'] ) ) { return $request_params['chatHistory']; } return array(); } /** * Prepare compact recent messages. * * @param array $messages Messages. * @param int $max_messages Max messages. * @return array Recent messages. */ private function prepare_recent_messages( $messages, $max_messages ) { $prepared = array(); foreach ( (array) $messages as $message ) { $role = isset( $message['role'] ) ? (string) $message['role'] : ''; if ( ! in_array( $role, array( 'user', 'assistant' ), true ) ) { continue; } $content = isset( $message['content'] ) ? trim( wp_strip_all_tags( (string) $message['content'] ) ) : ''; if ( '' === $content ) { continue; } $prepared[] = array( 'role' => $role, 'content' => $this->truncate_text( $content, 900 ), ); } if ( count( $prepared ) > $max_messages ) { $prepared = array_slice( $prepared, -1 * $max_messages ); } return $prepared; } /** * Avoid echoing the active user turn inside the saved-context excerpt. * * @param array $messages Recent messages. * @param string $active_user_message Active user message. * @return array Messages without duplicate active turn. */ private function remove_active_user_message( $messages, $active_user_message ) { $active_user_message = trim( wp_strip_all_tags( (string) $active_user_message ) ); if ( '' === $active_user_message || empty( $messages ) ) { return $messages; } for ( $i = count( $messages ) - 1; $i >= 0; $i-- ) { if ( 'user' !== ( $messages[ $i ]['role'] ?? '' ) ) { continue; } $content = trim( (string) ( $messages[ $i ]['content'] ?? '' ) ); if ( $content === $active_user_message ) { array_splice( $messages, $i, 1 ); } break; } return $messages; } /** * Resolve post config. * * @param array $saved_context Saved context package. * @param array $request_params Request params. * @return array Post config. */ private function resolve_post_config( $saved_context, $request_params ) { $config = $saved_context['post_config'] ?? array(); if ( ! empty( $request_params['postConfig'] ) && is_array( $request_params['postConfig'] ) ) { $config = wp_parse_args( $request_params['postConfig'], $config ); } return is_array( $config ) ? $config : array(); } /** * Resolve current plan. * * @param array $saved_context Saved context package. * @param array $request_params Request params. * @param int $post_id Post ID. * @return array|null Plan. */ private function resolve_plan( $saved_context, $request_params, $post_id ) { if ( ! empty( $saved_context['plan'] ) && is_array( $saved_context['plan'] ) ) { return $saved_context['plan']; } if ( ! empty( $request_params['plan'] ) && is_array( $request_params['plan'] ) ) { return $request_params['plan']; } if ( $post_id > 0 ) { $plan = get_post_meta( $post_id, '_wpaw_plan', true ); if ( is_array( $plan ) ) { return $plan; } } return null; } /** * Build compact working context. * * @param string $task Task name. * @param array $session_context Session context. * @param array $recent_messages Recent messages. * @param array $plan Current plan. * @param array $post_config Post config. * @param array $request_params Request params. * @return string Context text. */ private function build_working_context( $task, $session_context, $recent_messages, $plan, $post_config, $request_params ) { $sections = array(); $sections[] = "BACKEND CONTINUITY CONTEXT\nUse this compact WordPress-saved context as continuity. Do not assume OpenRouter remembers prior turns."; $sections[] = 'Current task: ' . sanitize_key( $task ); $summary = $session_context['working_summary']['text'] ?? ''; if ( '' !== trim( (string) $summary ) ) { $sections[] = "Working summary:\n" . $this->truncate_text( (string) $summary, 1600 ); } $config_summary = $this->summarize_post_config( $post_config ); if ( '' !== $config_summary ) { $sections[] = "Article configuration:\n" . $config_summary; } $plan_summary = $this->summarize_plan( $plan ); if ( '' !== $plan_summary ) { $sections[] = "Current plan:\n" . $plan_summary; } $decision_summary = $this->summarize_context_items( $session_context, 'decisions', 'Decisions' ); if ( '' !== $decision_summary ) { $sections[] = $decision_summary; } $rejection_summary = $this->summarize_context_items( $session_context, 'rejections', 'Rejected directions' ); if ( '' !== $rejection_summary ) { $sections[] = $rejection_summary; } if ( ! empty( $request_params['context'] ) ) { $sections[] = "User supplied context:\n" . $this->truncate_text( (string) $request_params['context'], 1600 ); } if ( ! empty( $recent_messages ) ) { $lines = array(); foreach ( $recent_messages as $message ) { $lines[] = ucfirst( $message['role'] ) . ': ' . $message['content']; } $sections[] = "Recent saved conversation excerpts:\n" . implode( "\n", $lines ); } return implode( "\n\n", array_filter( $sections ) ); } /** * Summarize post config. * * @param array $post_config Post config. * @return string Summary. */ private function summarize_post_config( $post_config ) { $lines = array(); $keys = array( 'article_length' => 'Article length', 'language' => 'Language', 'tone' => 'Tone', 'audience' => 'Audience', 'experience_level' => 'Experience level', 'seo_focus_keyword' => 'SEO focus keyword', 'seo_secondary_keywords' => 'SEO secondary keywords', ); foreach ( $keys as $key => $label ) { if ( isset( $post_config[ $key ] ) && '' !== trim( (string) $post_config[ $key ] ) ) { $lines[] = '- ' . $label . ': ' . trim( (string) $post_config[ $key ] ); } } if ( isset( $post_config['include_images'] ) ) { $lines[] = '- Include images: ' . ( $post_config['include_images'] ? 'yes' : 'no' ); } if ( isset( $post_config['web_search'] ) ) { $lines[] = '- Web search: ' . ( $post_config['web_search'] ? 'yes' : 'no' ); } return implode( "\n", $lines ); } /** * Summarize current plan. * * @param array|null $plan Plan. * @return string Summary. */ private function summarize_plan( $plan ) { if ( empty( $plan ) || ! is_array( $plan ) ) { return ''; } $lines = array(); if ( ! empty( $plan['title'] ) ) { $lines[] = 'Title: ' . $plan['title']; } if ( ! empty( $plan['sections'] ) && is_array( $plan['sections'] ) ) { foreach ( $plan['sections'] as $index => $section ) { $heading = $section['heading'] ?? $section['title'] ?? ''; if ( '' === trim( (string) $heading ) ) { continue; } $status = $section['status'] ?? 'pending'; $lines[] = sprintf( '%d. [%s] %s', $index + 1, $status, $heading ); } } return implode( "\n", $lines ); } /** * Summarize context array items. * * @param array $session_context Session context. * @param string $key Context key. * @param string $label Label. * @return string Summary. */ private function summarize_context_items( $session_context, $key, $label ) { if ( empty( $session_context[ $key ] ) || ! is_array( $session_context[ $key ] ) ) { return ''; } $lines = array(); foreach ( array_slice( $session_context[ $key ], -8 ) as $item ) { $summary = $item['summary'] ?? ''; if ( '' === trim( (string) $summary ) ) { continue; } $target = ! empty( $item['target'] ) ? '[' . $item['target'] . '] ' : ''; $lines[] = '- ' . $target . $summary; } return empty( $lines ) ? '' : $label . ":\n" . implode( "\n", $lines ); } /** * Get active content from request params. * * @param array $request_params Request params. * @return string Active content context. */ private function get_active_content( $request_params ) { $candidates = array( 'activeContent', 'blockContent', 'selectedText', 'sectionContent', 'articleContent', ); $lines = array(); foreach ( $candidates as $key ) { if ( ! empty( $request_params[ $key ] ) && is_string( $request_params[ $key ] ) ) { $lines[] = $key . ":\n" . $this->truncate_text( $request_params[ $key ], 2200 ); } } return empty( $lines ) ? '' : "ACTIVE CONTENT SLICE\n" . implode( "\n\n", $lines ); } /** * Build research context. * * @param array $session_context Session context. * @param array $request_params Request params. * @param int $limit Max snippets. * @return string Research context. */ private function build_research_context( $session_context, $request_params, $limit ) { if ( $limit <= 0 ) { return ''; } $items = array(); if ( ! empty( $session_context['research_notes'] ) && is_array( $session_context['research_notes'] ) ) { $items = array_merge( $items, $session_context['research_notes'] ); } if ( ! empty( $request_params['researchNotes'] ) && is_array( $request_params['researchNotes'] ) ) { $items = array_merge( $items, $request_params['researchNotes'] ); } $items = array_slice( $items, -1 * $limit ); $lines = array(); foreach ( $items as $item ) { if ( ! is_array( $item ) ) { continue; } $title = $item['title'] ?? $item['source'] ?? 'Research note'; $excerpt = $item['excerpt'] ?? $item['notes'] ?? ''; if ( '' === trim( (string) $excerpt ) ) { continue; } $lines[] = '- ' . $title . ': ' . $this->truncate_text( (string) $excerpt, 700 ); } return empty( $lines ) ? '' : "RELEVANT RESEARCH\n" . implode( "\n", $lines ); } /** * Count included research items. * * @param array $session_context Session context. * @param array $request_params Request params. * @param int $limit Max snippets. * @return int Count. */ private function count_research_items( $session_context, $request_params, $limit ) { if ( $limit <= 0 ) { return 0; } $count = 0; if ( ! empty( $session_context['research_notes'] ) && is_array( $session_context['research_notes'] ) ) { $count += count( $session_context['research_notes'] ); } if ( ! empty( $request_params['researchNotes'] ) && is_array( $request_params['researchNotes'] ) ) { $count += count( $request_params['researchNotes'] ); } return min( $limit, $count ); } /** * Truncate text safely. * * @param string $text Text. * @param int $limit Character limit. * @return string Truncated text. */ private function truncate_text( $text, $limit ) { $text = trim( (string) $text ); if ( strlen( $text ) <= $limit ) { return $text; } return substr( $text, 0, $limit ) . '...'; } /** * Estimate tokens from character length. * * @param string $text Text. * @return int Estimated tokens. */ private function estimate_tokens( $text ) { return (int) ceil( strlen( (string) $text ) / 4 ); } }