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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
use crate::raw::ext::Extensions;
use crate::raw::{shm, Content};
use serdev::{Deserialize, Serialize};
/// Represents lorebook (a.k.a. character book) data.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Lorebook {
/// Represents name of lorebook.
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// Represents description of lorebook.
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// Represents scan depth of lorebook.
///
/// ### Expected Behaviour
///
/// If this field is presented,
/// `keys` field in each [entry](LorebookEntry)
/// **SHOULD** be checked with only recent this number of messages.
///
/// If the context is not chat based,
/// or if the application doesn't support chat logs,
/// or if retrieving recent logs is impossible,
/// the application **SHOULD** ignore this field.
#[serde(skip_serializing_if = "Option::is_none")]
pub scan_depth: Option<u64>,
/// Represents token budget.
///
/// ### Expected Behaviour
///
/// If this field is presented,
/// and if total number of token exceeds this field,
/// the application **SHOULD** omit the lorebook whose priority is lowest.
///
/// If priority **and** insertion_order are not presented,
/// or if it's impossible to determine order of priorities,
/// or if token count cannot be determined,
/// implementation of omitting is application-specific.
///
/// ### Implementation Notes
///
/// The specification doesn't specify whether omitted entries can affect other entries,
/// Thus, to be consistent with `recursive_scanning`,
/// Omitted entries **SHOULD NOT** be scanned by other entries.
#[serde(skip_serializing_if = "Option::is_none")]
pub token_budget: Option<u64>,
/// Represents whether application should scan entries recursively.
///
/// ### Expected Behaviour
///
/// If this field is not presented,
/// default value is application-specific.
///
/// If this field is true,
/// contents of entries already matched **MAY** be re-scanned by unmatched entries.
/// Re-scanning process loops until there is no newly matched entry.
#[serde(skip_serializing_if = "Option::is_none")]
pub recursive_scanning: Option<bool>,
/// Represents application-specific extensions.
pub extensions: Extensions,
/// Represents entries of lorebook.
pub entries: Vec<shm::LorebookEntry>,
}
/// Represents whether the entry is placed before or after character definitions.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum EntryPosition {
BeforeChar,
AfterChar,
}
/// Represents the identifier of lorebook entry.
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash)]
#[serde(untagged)]
pub enum Id {
/// Numeric identifier.
///
/// Until version 2, numeric identifier is only valid form of identifier.
Number(u64),
/// String identifier.
///
/// String identifier is only valid version 3.
/// To be compatible with older versions,
/// Use [numeric identifier](Id::Number).
String(String),
}
/// Represents version-2-specific data of lorebook entry.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LorebookEntry {
/// Represents an array of activation keys.
///
/// ### Expected Behaviour
///
/// Entry is matched (activated) iff any item in the field is *matched* in *context*.
///
/// By default, string matching is ordinal inclusion test.
/// Currently, fields that can change string matching behaviour are:
///
/// - `use_regex` in [`v3::LorebookEntry`](crate::raw::v3::LorebookEntry).
/// - `case_sensitive` in this struct.
///
/// By default, context means message logs.
/// Regarding configuration of [`Lorebook`](Lorebook),
/// context can be extended to another lorebooks,
/// or be shrunken into recent messages.
pub keys: Vec<String>,
/// Represents content of entry.
pub content: Content,
/// Represents application-specific extensions.
pub extensions: Extensions,
/// Represents whether the entry is enabled.
///
/// ### Expected Behaviour
///
/// If the value is false,
/// the entry **MUST NOT** be considered as match,
/// regardless to whatever the other fields.
pub enabled: bool,
/// Represents insertion order of the entry.
///
/// ### Expected Behaviour
///
/// Lower value means the entry is added to prompt earlier.
///
/// If this field is presented and `priority` field is not presented,
/// The entry whose insertion order is lowest **MAY** be omitted first.
/// See `token_budget` field in [`Lorebook`](Lorebook).
pub insertion_order: u64,
/// Represents whether the match should be case-sensitive.
///
/// ### Expected Behaviour
///
/// If this field is not presented,
/// default value is application-specific.
///
/// If the value is true,
/// string matching of keys **SHOULD** be case-sensitive.
/// Otherwise,
/// string matching of keys **SHOULD** be case-insensitive.
///
/// ### Implementation Notes
///
/// Although the specification did not specify the default value of this field,
/// Regarding the purpose of key matching,
/// case-insensitive seems more proper option for key matching.
///
/// Therefore, Applications **SHOULD** use false as default value.
#[serde(skip_serializing_if = "Option::is_none")]
pub case_sensitive: Option<bool>,
/// Represents whether the entry is considered as matched regardless to keys.
///
/// ### Expected Behaviour
///
/// If `use_regex` in [`v3::LorebookEntry`](crate::raw::v3::LorebookEntry) is set to true,
/// this field **SHOULD** be ignored.
///
/// If the value is true,
/// the entry **MUST** be considered as matched regardless to `keys` and `secondary_keys`.
///
/// ### Implementation Notes
///
/// Although the specification did not specify omit of constant entry,
/// Regarding the purpose of `constant`,
/// Constant entries seem to be non-omittable.
///
/// Therefore, Until there is no more non-constant entry to omit,
/// Applications **SHOULD NOT** omit constant entry.
#[serde(skip_serializing_if = "Option::is_none")]
pub constant: Option<bool>,
/// Represents name of the entry.
///
/// ### Implementation Notes
///
/// Although the specification notes this field **MAY** be used to identify the entry,
/// also because the spec. notes this field is added for compatibility with *AgnAI* and *RisuAI*,
/// it seems inappropriate that use this field as unique identifier of the entry.
///
/// Therefore, Applications **SHOULD NOT** use this field as unique identifier of entry,
/// and **MAY** use index of entry as identifier instead.
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// Represents priority of the entry.
///
/// ### Implementation Notes
///
/// This field basically does same thing with `insertion_order` field,
/// and also the specification notes this field is add for compatibility with *AgnAI*.
///
/// Therefore, if distribution of `insertion_order` is not too dense to order entries,
/// Applications **SHOULD NOT** use this field to order entries.
///
/// Also, Applications **SHOULD** use only `insertion_order` for new character cards.
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<u64>,
/// Represents identifier of the entry.
///
/// ### Implementation Notes
///
/// Although the specification notes this field **MAY** be used to identify the entry,
/// also because the spec. notes this field is added for compatibility with *ST* and *RisuAI*,
/// it seems inappropriate that use this field as unique identifier of the entry.
///
/// Therefore, Applications **SHOULD NOT** use this field as unique identifier of entry.
/// and **MAY** use index of entry as identifier instead.
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Id>,
/// Represents extra comment.
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
/// Represents whether the `secondary_keys` field is used.
#[serde(skip_serializing_if = "Option::is_none")]
pub selective: Option<bool>,
/// Represents how the entry is placed regarding the position of character definitions.
#[serde(skip_serializing_if = "Option::is_none")]
pub position: Option<EntryPosition>,
/// Represents an array of secondary keys.
///
/// ### Expected Behaviour
///
/// If `selective` is not set to true,
/// or if `use_regex` is true,
/// This field **SHOULD** be ignored.
///
/// Otherwise,
/// all of secondary keys **SHOULD** be matched in context to activate entry
/// *(Note that conditions by other fields is also required)*.
///
/// ### Implementation Notes
///
/// Although the specification notes matching policy of secondary keys is application-specific,
/// regarding the purpose of the secondary key,
/// it seems appropriate that the matching policy is same between the key and the secondary key.
///
/// Therefore, Applications **SHOULD** use same matching policy for keys and secondary keys.
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub secondary_keys: Vec<String>,
/// ***Non-Standard Item** (observed from artefacts of RisuAI)*
///
/// Represents whether the entry is folder or regular entry.
///
/// ### Observations
///
/// Observed values from artefacts are `normal` and `folder`.
///
/// If the value is `normal`, the entry is just standard lorebook entry.
///
/// If the value is `folder`,
/// It's observed that `keys` has only one element,
/// and that element is used as internal identifier of folder in artefacts.
///
/// ### Implementation Notes
///
/// Application **SHOULD** exclude folder entries from matching target.
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
/// ***Non-Standard Item** (observed from artefacts of RisuAI)*
///
/// Represents what folder the entry belongs to.
///
/// ### Observations
///
/// Observed values are an element of `keys` in folder entries.
///
/// It seems entries struct filesystem-like hierarchy,
/// So this field can be not presented for
/// top entries (who doesn't belong to some folder).
///
/// ### Implementation Notes
///
/// Applications **MAY** implement hierarchy structure of entries
/// to support this system.
#[serde(skip_serializing_if = "Option::is_none")]
pub folder: Option<String>,
}