Repository navigation
Expand file tree
/
Copy pathclass-session-store.php
More file actions
124 lines (115 loc) · 3.91 KB
/
Copy pathclass-session-store.php
File metadata and controls
124 lines (115 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?php
/**
* Per-session conversation history.
*
* Prefers the agents-api conversation store (the substrate's own transcript
* runtime, resolved via the `wp_agent_conversation_store` filter — e.g. the
* built-in CPT store, or a host store) so we don't reinvent persistence. Falls
* back to a bounded transient when no store is registered, keeping the plugin
* usable on a bare agents-api install.
*
* History is {role, content} turns. To bound token cost we keep user events
* verbatim (they carry the state: what was clicked, added, submitted) plus a
* compact note per assistant render rather than the full HTML, and cap the count.
*
* @package AgentServedSite
*/
namespace AgentServedSite;
defined( 'ABSPATH' ) || exit;
class Session_Store {
const PREFIX = 'ass_hist_';
const TTL = HOUR_IN_SECONDS;
const MAX_MSGS = 24; // ~12 turns.
/**
* The registered agents-api conversation store, or null if none is active.
*
* Duck-typed rather than `instanceof` a fixed class: the store interface is
* namespaced (`AgentsAPI\Core\Database\Chat\WP_Agent_Conversation_Store`) and a
* host may register its own implementation, so we just require the methods we use.
*/
private static function store(): ?object {
if ( ! function_exists( 'apply_filters' ) ) {
return null;
}
$store = apply_filters( 'wp_agent_conversation_store', null );
if ( is_object( $store )
&& method_exists( $store, 'create_session' )
&& method_exists( $store, 'get_session' )
&& method_exists( $store, 'update_session' ) ) {
return $store;
}
return null;
}
/**
* Return a usable session id, minting a new one when starting fresh. With a
* store, the store mints the id (so get/update by id work); otherwise a UUID.
*/
public static function ensure( string $session_id ): string {
if ( '' !== $session_id ) {
return $session_id;
}
$store = self::store();
$scope_class = '\\AgentsAPI\\Core\\Workspace\\WP_Agent_Workspace_Scope';
if ( $store && class_exists( $scope_class ) ) {
try {
$workspace = $scope_class::from_parts( 'site', SLUG );
return (string) $store->create_session( $workspace, get_current_user_id(), SLUG, array(), 'chat' );
} catch ( \Throwable $e ) {
// Fall through to a plain UUID + transient history.
}
}
return wp_generate_uuid4();
}
/**
* @return array<int,array{role:string,content:string}>
*/
public static function get_messages( string $session_id ): array {
if ( '' === $session_id ) {
return array();
}
$store = self::store();
if ( $store ) {
try {
$session = $store->get_session( $session_id );
if ( is_array( $session ) && isset( $session['messages'] ) && is_array( $session['messages'] ) ) {
return $session['messages'];
}
return array();
} catch ( \Throwable $e ) {
// Fall through to transient.
}
}
$history = get_transient( self::key( $session_id ) );
return is_array( $history ) ? $history : array();
}
/**
* Replace the full transcript. Both backends store the complete array
* (the conversation store's update_session expects the whole transcript,
* not a delta).
*
* @param array<int,array{role:string,content:string}> $messages
* @param array<string,mixed> $metadata
*/
public static function set_messages( string $session_id, array $messages, array $metadata = array() ): void {
if ( '' === $session_id ) {
return;
}
if ( count( $messages ) > self::MAX_MSGS ) {
$messages = array_slice( $messages, -self::MAX_MSGS );
}
$store = self::store();
if ( $store ) {
try {
$model = (string) ( $metadata['model'] ?? '' );
$store->update_session( $session_id, $messages, $metadata, '', $model );
return;
} catch ( \Throwable $e ) {
// Fall through to transient.
}
}
set_transient( self::key( $session_id ), $messages, self::TTL );
}
private static function key( string $session_id ): string {
return self::PREFIX . md5( $session_id );
}
}