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.
- package/README.md +1 -0
- package/elastic/README.md +1 -1
- package/elastic/functions.md +5 -5
- package/elastic/pulse.md +2 -2
- package/package.json +2 -2
- package/scripts/deploy.js +68 -0
- package/src/actions.js +59 -0
- package/src/index.js +8 -6
- package/templates/.claude/skills/openkbs/SKILL.md +37 -8
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/README.md +55 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/instructions.txt +40 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/settings.json +41 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/openkbs.json +3 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/actions.js +141 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/handler.js +32 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/memoryHelpers.js +91 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onCronjob.js +105 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onPublicAPIRequest.js +165 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onRequest.js +2 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onResponse.js +2 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.js +74 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.json +3 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/index.mjs +228 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/package.json +7 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/index.mjs +287 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/package.json +10 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/settings.json +4 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/openkbs.json +16 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/index.html +658 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/settings.json +4 -0
- package/templates/.claude/skills/openkbs/patterns/cronjob-batch-processing.md +278 -0
- package/templates/.claude/skills/openkbs/patterns/cronjob-monitoring.md +341 -0
- package/templates/.claude/skills/openkbs/patterns/file-upload.md +205 -0
- package/templates/.claude/skills/openkbs/patterns/image-generation.md +139 -0
- package/templates/.claude/skills/openkbs/patterns/memory-system.md +264 -0
- package/templates/.claude/skills/openkbs/patterns/public-api-item-proxy.md +254 -0
- package/templates/.claude/skills/openkbs/patterns/scheduled-tasks.md +157 -0
- package/templates/.claude/skills/openkbs/patterns/telegram-webhook.md +424 -0
- package/templates/.claude/skills/openkbs/patterns/telegram.md +222 -0
- package/templates/.claude/skills/openkbs/patterns/vectordb-archive.md +231 -0
- package/templates/.claude/skills/openkbs/patterns/video-generation.md +145 -0
- package/templates/.claude/skills/openkbs/patterns/web-publishing.md +257 -0
- package/templates/.claude/skills/openkbs/reference/backend-sdk.md +13 -2
- package/templates/.claude/skills/openkbs/reference/elastic-services.md +61 -29
- package/templates/platform/README.md +35 -0
- package/templates/platform/agents/assistant/app/icon.png +0 -0
- package/templates/platform/agents/assistant/app/instructions.txt +13 -0
- package/templates/platform/agents/assistant/app/settings.json +13 -0
- package/templates/platform/agents/assistant/src/Events/actions.js +50 -0
- package/templates/platform/agents/assistant/src/Events/handler.js +54 -0
- package/templates/platform/agents/assistant/src/Events/onRequest.js +3 -0
- package/templates/platform/agents/assistant/src/Events/onRequest.json +5 -0
- package/templates/platform/agents/assistant/src/Events/onResponse.js +3 -0
- package/templates/platform/agents/assistant/src/Events/onResponse.json +5 -0
- package/templates/platform/agents/assistant/src/Frontend/contentRender.js +27 -0
- package/templates/platform/agents/assistant/src/Frontend/contentRender.json +10 -0
- package/templates/platform/functions/api/index.mjs +63 -0
- package/templates/platform/functions/api/package.json +4 -0
- package/templates/platform/openkbs.json +19 -0
- package/templates/platform/site/index.html +75 -0
- package/version.json +3 -3
- package/templates/.claude/skills/openkbs/examples/ai-copywriter-agent/scripts/run_job.js +0 -26
- 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
|