openkbs 0.0.66 → 0.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +1 -0
  2. package/elastic/README.md +1 -1
  3. package/elastic/functions.md +5 -5
  4. package/elastic/pulse.md +2 -2
  5. package/package.json +2 -2
  6. package/scripts/deploy.js +68 -0
  7. package/src/actions.js +59 -0
  8. package/src/index.js +8 -6
  9. package/templates/.claude/skills/openkbs/SKILL.md +37 -8
  10. package/templates/.claude/skills/openkbs/examples/monitoring-bot/README.md +55 -0
  11. package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/instructions.txt +40 -0
  12. package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/settings.json +41 -0
  13. package/templates/.claude/skills/openkbs/examples/monitoring-bot/openkbs.json +3 -0
  14. package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/actions.js +141 -0
  15. package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/handler.js +32 -0
  16. package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/memoryHelpers.js +91 -0
  17. package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onCronjob.js +105 -0
  18. package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onPublicAPIRequest.js +165 -0
  19. package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onRequest.js +2 -0
  20. package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onResponse.js +2 -0
  21. package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.js +74 -0
  22. package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.json +3 -0
  23. package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/index.mjs +228 -0
  24. package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/package.json +7 -0
  25. package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/index.mjs +287 -0
  26. package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/package.json +10 -0
  27. package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/settings.json +4 -0
  28. package/templates/.claude/skills/openkbs/examples/nodejs-demo/openkbs.json +16 -0
  29. package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/index.html +658 -0
  30. package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/settings.json +4 -0
  31. package/templates/.claude/skills/openkbs/patterns/cronjob-batch-processing.md +278 -0
  32. package/templates/.claude/skills/openkbs/patterns/cronjob-monitoring.md +341 -0
  33. package/templates/.claude/skills/openkbs/patterns/file-upload.md +205 -0
  34. package/templates/.claude/skills/openkbs/patterns/image-generation.md +139 -0
  35. package/templates/.claude/skills/openkbs/patterns/memory-system.md +264 -0
  36. package/templates/.claude/skills/openkbs/patterns/public-api-item-proxy.md +254 -0
  37. package/templates/.claude/skills/openkbs/patterns/scheduled-tasks.md +157 -0
  38. package/templates/.claude/skills/openkbs/patterns/telegram-webhook.md +424 -0
  39. package/templates/.claude/skills/openkbs/patterns/telegram.md +222 -0
  40. package/templates/.claude/skills/openkbs/patterns/vectordb-archive.md +231 -0
  41. package/templates/.claude/skills/openkbs/patterns/video-generation.md +145 -0
  42. package/templates/.claude/skills/openkbs/patterns/web-publishing.md +257 -0
  43. package/templates/.claude/skills/openkbs/reference/backend-sdk.md +13 -2
  44. package/templates/.claude/skills/openkbs/reference/elastic-services.md +61 -29
  45. package/templates/platform/README.md +35 -0
  46. package/templates/platform/agents/assistant/app/icon.png +0 -0
  47. package/templates/platform/agents/assistant/app/instructions.txt +13 -0
  48. package/templates/platform/agents/assistant/app/settings.json +13 -0
  49. package/templates/platform/agents/assistant/src/Events/actions.js +50 -0
  50. package/templates/platform/agents/assistant/src/Events/handler.js +54 -0
  51. package/templates/platform/agents/assistant/src/Events/onRequest.js +3 -0
  52. package/templates/platform/agents/assistant/src/Events/onRequest.json +5 -0
  53. package/templates/platform/agents/assistant/src/Events/onResponse.js +3 -0
  54. package/templates/platform/agents/assistant/src/Events/onResponse.json +5 -0
  55. package/templates/platform/agents/assistant/src/Frontend/contentRender.js +27 -0
  56. package/templates/platform/agents/assistant/src/Frontend/contentRender.json +10 -0
  57. package/templates/platform/functions/api/index.mjs +63 -0
  58. package/templates/platform/functions/api/package.json +4 -0
  59. package/templates/platform/openkbs.json +19 -0
  60. package/templates/platform/site/index.html +75 -0
  61. package/version.json +3 -3
  62. package/templates/.claude/skills/openkbs/examples/ai-copywriter-agent/scripts/run_job.js +0 -26
  63. package/templates/.claude/skills/openkbs/examples/ai-copywriter-agent/scripts/utils/agent_client.js +0 -265
@@ -0,0 +1,205 @@
1
+ # File Upload Pattern
2
+
3
+ Complete working code for uploading files to KB storage using presigned URLs.
4
+
5
+ ## actions.js
6
+
7
+ ```javascript
8
+ // Upload files from URLs to permanent storage
9
+ [/<uploadFiles>([\s\S]*?)<\/uploadFiles>/s, async (match) => {
10
+ try {
11
+ const content = match[1].trim();
12
+
13
+ // Parse URLs - support newline-separated or JSON array
14
+ let fileUrls = [];
15
+ try {
16
+ const parsed = JSON.parse(content);
17
+ if (Array.isArray(parsed)) fileUrls = parsed;
18
+ } catch (e) {
19
+ // Not JSON, treat as newline-separated URLs
20
+ fileUrls = content
21
+ .split('\n')
22
+ .map(line => line.trim())
23
+ .filter(line => line.startsWith('http'));
24
+ }
25
+
26
+ if (fileUrls.length === 0) {
27
+ return { error: 'No valid URLs found', ...meta };
28
+ }
29
+
30
+ const uploadResults = [];
31
+
32
+ for (let i = 0; i < fileUrls.length; i++) {
33
+ const fileUrl = fileUrls[i];
34
+
35
+ try {
36
+ // 1. Fetch the file
37
+ const fileResponse = await axios.get(fileUrl, { responseType: 'arraybuffer' });
38
+ const fileBuffer = fileResponse.data;
39
+
40
+ // 2. Determine filename and content type
41
+ const urlPath = new URL(fileUrl).pathname;
42
+ let filename = urlPath.split('/').pop();
43
+
44
+ // Generate filename if none
45
+ if (!filename || !filename.includes('.')) {
46
+ const contentType = fileResponse.headers['content-type'] || 'application/octet-stream';
47
+ const extMap = {
48
+ 'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif',
49
+ 'image/webp': 'webp', 'video/mp4': 'mp4', 'application/pdf': 'pdf'
50
+ };
51
+ const ext = extMap[contentType] || 'bin';
52
+ filename = `file_${i + 1}.${ext}`;
53
+ }
54
+
55
+ // Add timestamp for uniqueness
56
+ const timestamp = Date.now();
57
+ const parts = filename.split('.');
58
+ const ext = parts.pop();
59
+ filename = `${parts.join('.')}_${timestamp}.${ext}`;
60
+
61
+ const contentType = fileResponse.headers['content-type'] || 'application/octet-stream';
62
+
63
+ // 3. Get presigned URL
64
+ const presignedUrl = await openkbs.kb({
65
+ action: 'createPresignedURL',
66
+ namespace: 'files',
67
+ fileName: filename,
68
+ fileType: contentType,
69
+ presignedOperation: 'putObject'
70
+ });
71
+
72
+ // 4. Upload to S3
73
+ await axios.put(presignedUrl, fileBuffer, {
74
+ headers: {
75
+ 'Content-Type': contentType,
76
+ 'Content-Length': fileBuffer.length
77
+ }
78
+ });
79
+
80
+ // 5. Construct public URL
81
+ const publicUrl = `https://yourdomain.com/media/${filename}`;
82
+
83
+ uploadResults.push({
84
+ sourceUrl: fileUrl,
85
+ status: 'success',
86
+ uploadedUrl: publicUrl,
87
+ filename,
88
+ size: fileBuffer.length
89
+ });
90
+
91
+ } catch (error) {
92
+ uploadResults.push({
93
+ sourceUrl: fileUrl,
94
+ status: 'failed',
95
+ error: error.message
96
+ });
97
+ }
98
+ }
99
+
100
+ const successCount = uploadResults.filter(r => r.status === 'success').length;
101
+
102
+ return {
103
+ type: "FILES_UPLOADED",
104
+ summary: `Uploaded ${successCount} of ${fileUrls.length} files`,
105
+ results: uploadResults,
106
+ uploadedUrls: uploadResults.filter(r => r.status === 'success').map(r => r.uploadedUrl),
107
+ ...meta
108
+ };
109
+
110
+ } catch (e) {
111
+ return { error: e.message, ...meta };
112
+ }
113
+ }]
114
+ ```
115
+
116
+ ## Direct Image Upload (from generateImage)
117
+
118
+ ```javascript
119
+ // Helper for uploading generated images
120
+ const uploadGeneratedImage = async (base64Data, meta) => {
121
+ const fileName = `image-${Date.now()}-${Math.random().toString(36).substring(7)}.png`;
122
+ const uploadResult = await openkbs.uploadImage(base64Data, fileName, 'image/png');
123
+ return {
124
+ type: 'CHAT_IMAGE',
125
+ data: { imageUrl: uploadResult.url },
126
+ ...meta
127
+ };
128
+ };
129
+ ```
130
+
131
+ ## HTML/Text Upload
132
+
133
+ ```javascript
134
+ // Upload HTML content
135
+ const uploadHtmlContent = async (htmlContent, filename) => {
136
+ // Get presigned URL
137
+ const presignedUrl = await openkbs.kb({
138
+ action: 'createPresignedURL',
139
+ namespace: 'files',
140
+ fileName: filename,
141
+ fileType: 'text/html',
142
+ presignedOperation: 'putObject'
143
+ });
144
+
145
+ // Upload as UTF-8 Buffer
146
+ const htmlBuffer = Buffer.from(htmlContent, 'utf8');
147
+ await axios.put(presignedUrl, htmlBuffer, {
148
+ headers: {
149
+ 'Content-Type': 'text/html',
150
+ 'Content-Length': htmlBuffer.length
151
+ }
152
+ });
153
+
154
+ return `https://yourdomain.com/media/${filename}`;
155
+ };
156
+ ```
157
+
158
+ ## Frontend File Upload (contentRender.js)
159
+
160
+ ```javascript
161
+ // Using openkbs.Files API in frontend
162
+ const handleFileUpload = async (file, onProgress) => {
163
+ try {
164
+ await openkbs.Files.uploadFileAPI(file, 'files', onProgress);
165
+ const publicUrl = `https://yourdomain.com/media/${file.name}`;
166
+ return publicUrl;
167
+ } catch (error) {
168
+ console.error('Upload failed:', error);
169
+ throw error;
170
+ }
171
+ };
172
+
173
+ // List files
174
+ const files = await openkbs.Files.listFiles('files');
175
+ // Returns: [{ Key: 'files/kbId/filename.jpg', Size: 12345 }]
176
+
177
+ // Delete file
178
+ await openkbs.Files.deleteRawKBFile('filename.jpg', 'files');
179
+ ```
180
+
181
+ ## instructions.txt (LLM prompt)
182
+
183
+ ```
184
+ File Upload:
185
+
186
+ Upload files from URLs:
187
+ <uploadFiles>
188
+ https://example.com/image1.jpg
189
+ https://example.com/document.pdf
190
+ </uploadFiles>
191
+
192
+ Or as JSON array:
193
+ <uploadFiles>["https://example.com/file1.jpg", "https://example.com/file2.png"]</uploadFiles>
194
+
195
+ Files are stored permanently and accessible via CDN.
196
+ ```
197
+
198
+ ## Key Points
199
+
200
+ 1. **Presigned URL flow**: Get URL → Upload to S3 → Construct public URL
201
+ 2. **CDN access**: Files accessible at `yourdomain.com/media/filename`
202
+ 3. **Content-Type matters**: Set correct MIME type for proper serving
203
+ 4. **Buffer for text**: Use `Buffer.from(content, 'utf8')` for text/HTML
204
+ 5. **Timestamps**: Add to filename for uniqueness
205
+ 6. **openkbs.uploadImage**: Shortcut for base64 image upload
@@ -0,0 +1,139 @@
1
+ # Image Generation Pattern
2
+
3
+ Complete working code for AI image generation with proper upload and display.
4
+
5
+ ## actions.js
6
+
7
+ ```javascript
8
+ // Helper - reuse across all image generation commands
9
+ const uploadGeneratedImage = async (base64Data, meta) => {
10
+ const fileName = `image-${Date.now()}-${Math.random().toString(36).substring(7)}.png`;
11
+ const uploadResult = await openkbs.uploadImage(base64Data, fileName, 'image/png');
12
+ return {
13
+ type: 'CHAT_IMAGE', // Core magic type - renders image in chat
14
+ data: { imageUrl: uploadResult.url },
15
+ ...meta
16
+ };
17
+ };
18
+
19
+ // Command handler
20
+ [/<createAIImage>([\s\S]*?)<\/createAIImage>/s, async (match) => {
21
+ try {
22
+ const data = JSON.parse(match[1].trim());
23
+
24
+ const model = data.model || 'gemini-2.5-flash-image';
25
+ const params = { model, n: 1 };
26
+
27
+ // Gemini-specific params
28
+ if (model.includes('gemini')) {
29
+ const validAspectRatios = ['1:1', '2:3', '3:2', '3:4', '4:3', '4:5', '5:4', '9:16', '16:9', '21:9'];
30
+ params.aspect_ratio = validAspectRatios.includes(data.aspect_ratio) ? data.aspect_ratio : '1:1';
31
+ if (data.imageUrls?.length > 0) params.imageUrls = data.imageUrls;
32
+ }
33
+
34
+ // GPT-specific params
35
+ if (model.includes('gpt')) {
36
+ const validSizes = ['1024x1024', '1536x1024', '1024x1536', 'auto'];
37
+ params.size = validSizes.includes(data.size) ? data.size : '1024x1024';
38
+ params.quality = 'high';
39
+ }
40
+
41
+ const images = await openkbs.generateImage(data.prompt, params);
42
+ return await uploadGeneratedImage(images[0].b64_json, meta);
43
+ } catch (error) {
44
+ return { error: error.message || 'Image creation failed', ...meta };
45
+ }
46
+ }]
47
+ ```
48
+
49
+ ## contentRender.js
50
+
51
+ ```javascript
52
+ import ImageWithDownload from './ImageWithDownload';
53
+
54
+ // In isVisualResult function
55
+ const isVisualResult = (r) => {
56
+ return (r?.type === 'CHAT_IMAGE' && r?.data?.imageUrl);
57
+ };
58
+
59
+ // In renderVisualResults function
60
+ if (item?.type === 'CHAT_IMAGE' && item?.data?.imageUrl) {
61
+ return (
62
+ <div key={`img-${idx}`} style={{ flex: '1 1 calc(50% - 6px)', minWidth: 200, maxWidth: 400 }}>
63
+ <ImageWithDownload imageUrl={item.data.imageUrl} />
64
+ </div>
65
+ );
66
+ }
67
+ ```
68
+
69
+ ## ImageWithDownload.js Component
70
+
71
+ ```javascript
72
+ import React from 'react';
73
+ import { IconButton, Box } from '@mui/material';
74
+ import DownloadIcon from '@mui/icons-material/Download';
75
+
76
+ const ImageWithDownload = ({ imageUrl }) => {
77
+ const handleDownload = async () => {
78
+ try {
79
+ const response = await fetch(imageUrl);
80
+ const blob = await response.blob();
81
+ const url = window.URL.createObjectURL(blob);
82
+ const a = document.createElement('a');
83
+ a.href = url;
84
+ a.download = `image-${Date.now()}.png`;
85
+ document.body.appendChild(a);
86
+ a.click();
87
+ document.body.removeChild(a);
88
+ window.URL.revokeObjectURL(url);
89
+ } catch (error) {
90
+ console.error('Download failed:', error);
91
+ }
92
+ };
93
+
94
+ return (
95
+ <Box sx={{ position: 'relative', display: 'inline-block' }}>
96
+ <img
97
+ src={imageUrl}
98
+ alt="Generated"
99
+ style={{ maxWidth: '100%', borderRadius: 8 }}
100
+ />
101
+ <IconButton
102
+ onClick={handleDownload}
103
+ sx={{
104
+ position: 'absolute',
105
+ top: 8,
106
+ right: 8,
107
+ backgroundColor: 'rgba(255,255,255,0.9)',
108
+ '&:hover': { backgroundColor: 'white' }
109
+ }}
110
+ >
111
+ <DownloadIcon />
112
+ </IconButton>
113
+ </Box>
114
+ );
115
+ };
116
+
117
+ export default ImageWithDownload;
118
+ ```
119
+
120
+ ## instructions.txt (LLM prompt)
121
+
122
+ ```
123
+ To generate an image:
124
+ <createAIImage>{"prompt": "detailed description", "model": "gemini-2.5-flash-image", "aspect_ratio": "16:9"}</createAIImage>
125
+
126
+ Models:
127
+ - gemini-2.5-flash-image: General images, supports editing with imageUrls
128
+ - gpt-image-1: Better for text in images
129
+
130
+ Gemini params: aspect_ratio (1:1, 16:9, 9:16, etc.), imageUrls (for editing)
131
+ GPT params: size (1024x1024, 1536x1024, 1024x1536)
132
+ ```
133
+
134
+ ## Key Points
135
+
136
+ 1. **Always use helper** - `uploadGeneratedImage` converts base64 → URL → CHAT_IMAGE
137
+ 2. **CHAT_IMAGE is core magic** - OpenKBS renders it automatically as image
138
+ 3. **Spread ...meta** - Preserves message context for proper rendering
139
+ 4. **Error handling** - Return error object with ...meta, not throw
@@ -0,0 +1,264 @@
1
+ # Memory System Pattern
2
+
3
+ Complete working code for memory CRUD operations with atomic updates.
4
+
5
+ ## memoryHelpers.js
6
+
7
+ Create this file for reusable memory operations:
8
+
9
+ ```javascript
10
+ // Memory Helpers - Atomic operations without race conditions
11
+
12
+ /**
13
+ * Generic upsert (update or create)
14
+ */
15
+ async function _upsertItem(itemType, itemId, body) {
16
+ try {
17
+ await openkbs.updateItem({ itemType, itemId, body });
18
+ } catch (e) {
19
+ await openkbs.createItem({ itemType, itemId, body });
20
+ }
21
+ return { success: true, itemId };
22
+ }
23
+
24
+ /**
25
+ * Set a memory value atomically
26
+ * @param {string} itemId - Must start with "memory_"
27
+ * @param {*} value - Any JSON-serializable value
28
+ * @param {number} expirationInMinutes - Optional expiration
29
+ */
30
+ export async function setMemoryValue(itemId, value, expirationInMinutes = null) {
31
+ if (!itemId.startsWith('memory_')) {
32
+ throw new Error(`Invalid itemId: "${itemId}". Must start with "memory_"`);
33
+ }
34
+
35
+ const body = {
36
+ value,
37
+ updatedAt: new Date().toISOString()
38
+ };
39
+
40
+ if (expirationInMinutes != null) {
41
+ body.exp = new Date(Date.now() + expirationInMinutes * 60 * 1000).toISOString();
42
+ }
43
+
44
+ return _upsertItem('memory', itemId, body);
45
+ }
46
+
47
+ /**
48
+ * Delete any item by itemId
49
+ */
50
+ export async function deleteItem(itemId) {
51
+ try {
52
+ await openkbs.deleteItem(itemId);
53
+ return { success: true };
54
+ } catch (e) {
55
+ return { success: false, error: e.message };
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Set agent setting (for configuration that persists)
61
+ */
62
+ export async function setAgentSetting(itemId, value) {
63
+ if (!itemId.startsWith('agent_')) {
64
+ itemId = `agent_${itemId}`;
65
+ }
66
+ return _upsertItem('agent', itemId, { value, updatedAt: new Date().toISOString() });
67
+ }
68
+
69
+ /**
70
+ * Get agent setting
71
+ */
72
+ export async function getAgentSetting(itemId) {
73
+ try {
74
+ const item = await openkbs.getItem(itemId);
75
+ return item?.item?.body?.value;
76
+ } catch (e) {
77
+ return null;
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## actions.js
83
+
84
+ ```javascript
85
+ import { setMemoryValue, deleteItem } from './memoryHelpers.js';
86
+
87
+ // Set memory command
88
+ [/<setMemory>([\s\S]*?)<\/setMemory>/s, async (match) => {
89
+ try {
90
+ const data = JSON.parse(match[1].trim());
91
+
92
+ if (!data.itemId?.startsWith('memory_')) {
93
+ return {
94
+ type: "MEMORY_ERROR",
95
+ error: "itemId must start with 'memory_'",
96
+ _meta_actions: ["REQUEST_CHAT_MODEL"]
97
+ };
98
+ }
99
+
100
+ await setMemoryValue(data.itemId, data.value, data.expirationInMinutes);
101
+
102
+ return {
103
+ type: "MEMORY_UPDATED",
104
+ itemId: data.itemId,
105
+ expires: data.expirationInMinutes ? `in ${data.expirationInMinutes} minutes` : 'never',
106
+ _meta_actions: ["REQUEST_CHAT_MODEL"]
107
+ };
108
+ } catch (e) {
109
+ return {
110
+ type: "MEMORY_ERROR",
111
+ error: e.message,
112
+ _meta_actions: ["REQUEST_CHAT_MODEL"]
113
+ };
114
+ }
115
+ }],
116
+
117
+ // Delete item command
118
+ [/<deleteItem>([\s\S]*?)<\/deleteItem>/s, async (match) => {
119
+ try {
120
+ const data = JSON.parse(match[1].trim());
121
+ const result = await deleteItem(data.itemId);
122
+
123
+ if (!result.success) {
124
+ return {
125
+ type: "DELETE_ERROR",
126
+ error: result.error || "Failed to delete item",
127
+ _meta_actions: ["REQUEST_CHAT_MODEL"]
128
+ };
129
+ }
130
+
131
+ return {
132
+ type: "ITEM_DELETED",
133
+ itemId: data.itemId,
134
+ _meta_actions: ["REQUEST_CHAT_MODEL"]
135
+ };
136
+ } catch (e) {
137
+ return {
138
+ type: "DELETE_ERROR",
139
+ error: e.message,
140
+ _meta_actions: ["REQUEST_CHAT_MODEL"]
141
+ };
142
+ }
143
+ }]
144
+ ```
145
+
146
+ ## onRequest.js - Inject Memory into Context
147
+
148
+ ```javascript
149
+ export const handler = async (event) => {
150
+ // Fetch all memory items to inject into LLM context
151
+ const memoryItems = await openkbs.fetchItems({
152
+ itemType: 'memory',
153
+ beginsWith: 'memory_',
154
+ limit: 100
155
+ });
156
+
157
+ // Format for context injection
158
+ let memoryContext = '';
159
+ if (memoryItems?.items?.length > 0) {
160
+ memoryContext = '\n\n## Current Memory State:\n';
161
+ for (const { meta, item } of memoryItems.items) {
162
+ const value = typeof item.body.value === 'string'
163
+ ? item.body.value
164
+ : JSON.stringify(item.body.value);
165
+ memoryContext += `- ${meta.itemId}: ${value}\n`;
166
+ }
167
+ }
168
+
169
+ // Inject into system message
170
+ return {
171
+ ...event,
172
+ payload: {
173
+ ...event.payload,
174
+ messages: event.payload.messages.map((msg, idx) => {
175
+ if (idx === 0 && msg.role === 'system') {
176
+ return { ...msg, content: msg.content + memoryContext };
177
+ }
178
+ return msg;
179
+ })
180
+ }
181
+ };
182
+ };
183
+ ```
184
+
185
+ ## instructions.txt (LLM prompt)
186
+
187
+ ```
188
+ Memory Commands:
189
+
190
+ Save to memory:
191
+ <setMemory>{"itemId": "memory_user_preference", "value": "dark mode"}</setMemory>
192
+
193
+ Save with expiration (60 minutes):
194
+ <setMemory>{"itemId": "memory_session_data", "value": {...}, "expirationInMinutes": 60}</setMemory>
195
+
196
+ Delete item:
197
+ <deleteItem>{"itemId": "memory_old_data"}</deleteItem>
198
+
199
+ Memory items are automatically loaded into your context. Check "Current Memory State" section.
200
+ ```
201
+
202
+ ## settings.json - Priority Items Configuration
203
+
204
+ Priority items are **automatically injected** into LLM context without needing onRequest.js:
205
+
206
+ ```json
207
+ {
208
+ "model": "gemini-3-pro",
209
+ "embeddingModel": "text-embedding-3-large",
210
+ "embeddingDimension": 3072,
211
+ "searchEngine": "VectorDB",
212
+ "itemTypes": {
213
+ "memory": {
214
+ "attributes": [
215
+ { "attrName": "itemId", "attrType": "itemId", "encrypted": false },
216
+ { "attrName": "body", "attrType": "body", "encrypted": true }
217
+ ]
218
+ },
219
+ "archive": {
220
+ "attributes": [
221
+ { "attrName": "itemId", "attrType": "itemId", "encrypted": false },
222
+ { "attrName": "body", "attrType": "body", "encrypted": true }
223
+ ]
224
+ }
225
+ },
226
+ "options": {
227
+ "priorityItems": [
228
+ { "limit": 100, "prefix": "memory" },
229
+ { "limit": 50, "prefix": "agent" }
230
+ ],
231
+ "vectorDBMaxTokens": 25000,
232
+ "vectorDBTopK": 30,
233
+ "vectorDBMinScore": 90
234
+ }
235
+ }
236
+ ```
237
+
238
+ ### How Priority Items Work
239
+
240
+ 1. **Auto-injection** - Items matching prefix are automatically added to LLM context
241
+ 2. **No onRequest.js needed** - OpenKBS core handles this
242
+ 3. **Limit per prefix** - Control how many items are injected
243
+ 4. **Multiple prefixes** - Can have memory_, agent_, telegram_, etc.
244
+
245
+ ### VectorDB for Long-term Memory
246
+
247
+ For semantic search (archive), configure:
248
+ - `searchEngine: "VectorDB"` - Enable vector search
249
+ - `embeddingModel` - Which model to use for embeddings
250
+ - `embeddingDimension` - Must match model (3072 for text-embedding-3-large)
251
+ - `vectorDBTopK` - How many results to return
252
+ - `vectorDBMinScore` - Minimum similarity score (0-100)
253
+
254
+ ## Key Points
255
+
256
+ 1. **Prefix requirement** - All memory items must start with `memory_`
257
+ 2. **Upsert pattern** - Try update first, create if fails (handles both new and existing)
258
+ 3. **Expiration** - Use `exp` field with ISO timestamp for auto-cleanup
259
+ 4. **Auto-encryption** - Body is encrypted automatically by OpenKBS
260
+ 5. **REQUEST_CHAT_MODEL** - Memory operations continue LLM loop for confirmation
261
+ 6. **Priority items** - Configure in settings.json for auto-injection into context
262
+ 7. **Two storage types**:
263
+ - **Priority items** (memory_, agent_) - Fast key-value, auto-injected
264
+ - **VectorDB items** (archive_) - Semantic search, for long-term memory