127 lines
3.2 KiB
PHP
127 lines
3.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Context;
|
|
|
|
/**
|
|
* ContextService
|
|
*
|
|
* Manages conversational history persistence and retrieval.
|
|
*
|
|
* Responsibilities:
|
|
* - Persist completed conversation turns (append-only)
|
|
* - Provide recent or extended conversation context
|
|
* - Resolve history storage paths safely
|
|
*
|
|
* Non-responsibilities:
|
|
* - No follow-up detection
|
|
* - No prompt semantics
|
|
* - No interpretation of user intent
|
|
*
|
|
* Context levels:
|
|
* - Regular context: last N lines (default)
|
|
* - Full context: extended history for special cases
|
|
*/
|
|
final class ContextService
|
|
{
|
|
private string $historyDir;
|
|
|
|
/**
|
|
* Number of lines included in regular context.
|
|
* Intended for normal conversational continuity.
|
|
*/
|
|
private int $maxRegularLines = 20;
|
|
|
|
/**
|
|
* Number of lines included in full context.
|
|
* Intended for exceptional or diagnostic scenarios.
|
|
*/
|
|
private int $maxFullLines = 500;
|
|
|
|
public function __construct(
|
|
string $historyDir,
|
|
string $projectDir,
|
|
) {
|
|
/**
|
|
* Normalize history directory:
|
|
* - Allow relative paths in env (e.g. "var/agent-history")
|
|
* - Always resolve to an absolute path based on project root
|
|
*/
|
|
$historyDir = rtrim($historyDir, '/');
|
|
|
|
if (!str_starts_with($historyDir, '/')) {
|
|
$historyDir = rtrim($projectDir, '/') . '/' . ltrim($historyDir, '/');
|
|
}
|
|
|
|
$this->historyDir = $historyDir;
|
|
|
|
// Ensure directory exists
|
|
if (!is_dir($this->historyDir)) {
|
|
mkdir($this->historyDir, 0777, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the conversation context for a given user.
|
|
*
|
|
* @param string $userId Stable client identifier
|
|
* @param bool $full Whether to load extended history
|
|
*/
|
|
public function buildUserContext(string $userId, bool $full = true): string
|
|
{
|
|
$path = $this->getHistoryPath($userId);
|
|
|
|
if (!is_file($path)) {
|
|
return '';
|
|
}
|
|
|
|
$lines = file($path, FILE_IGNORE_NEW_LINES);
|
|
if ($lines === false) {
|
|
return '';
|
|
}
|
|
|
|
$maxLines = $full ? $this->maxFullLines : $this->maxRegularLines;
|
|
$selected = array_slice($lines, -$maxLines);
|
|
|
|
return implode("\n", $selected);
|
|
}
|
|
|
|
/**
|
|
* Appends a completed interaction to the user's history.
|
|
*
|
|
* Format (append-only):
|
|
* Question: <user prompt>
|
|
* <assistant response>
|
|
*/
|
|
public function appendHistory(string $userId, string $prompt, string $response): void
|
|
{
|
|
$path = $this->getHistoryPath($userId);
|
|
|
|
$entry = "Question: {$prompt}\n{$response}\n";
|
|
file_put_contents($path, $entry, FILE_APPEND | LOCK_EX);
|
|
}
|
|
|
|
/**
|
|
* Deletes the complete conversation history for a user.
|
|
*/
|
|
public function deleteHistory(string $userId): void
|
|
{
|
|
$path = $this->getHistoryPath($userId);
|
|
|
|
if (is_file($path)) {
|
|
unlink($path);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolves the absolute history file path for a user.
|
|
*/
|
|
private function getHistoryPath(string $userId): string
|
|
{
|
|
$safeUserId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $userId);
|
|
|
|
return $this->historyDir . '/' . $safeUserId . '.txt';
|
|
}
|
|
}
|