@vox-ai-app/storage 1.0.2 → 1.0.3

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.
@@ -0,0 +1,105 @@
1
+ export function vectorUpsert(db, collection, id, embedding, metadata = {}) {
2
+ const buffer = Buffer.from(new Float32Array(embedding).buffer)
3
+ const now = new Date().toISOString()
4
+
5
+ db.prepare(
6
+ `INSERT INTO vectors (id, collection, embedding, metadata, created_at, updated_at)
7
+ VALUES (?, ?, ?, ?, ?, ?)
8
+ ON CONFLICT (id, collection)
9
+ DO UPDATE SET embedding = excluded.embedding, metadata = excluded.metadata, updated_at = excluded.updated_at`
10
+ ).run(id, collection, buffer, JSON.stringify(metadata), now, now)
11
+ }
12
+
13
+ export function vectorSearch(db, collection, queryEmbedding, queryText, topK = 5) {
14
+ const rows = db
15
+ .prepare(`SELECT id, embedding, metadata FROM vectors WHERE collection = ?`)
16
+ .all(collection)
17
+
18
+ if (rows.length === 0) return []
19
+
20
+ const candidates = rows.map((row) => {
21
+ const stored = new Float32Array(
22
+ row.embedding.buffer,
23
+ row.embedding.byteOffset,
24
+ row.embedding.byteLength / 4
25
+ )
26
+ const meta = JSON.parse(row.metadata)
27
+ return {
28
+ id: row.id,
29
+ metadata: meta,
30
+ score: cosineSimilarity(queryEmbedding, stored)
31
+ }
32
+ })
33
+
34
+ const candidateLimit = Math.max(topK * 5, topK)
35
+ candidates.sort((a, b) => b.score - a.score)
36
+ const topCandidates = candidates.slice(0, candidateLimit)
37
+
38
+ if (!queryText) return topCandidates.slice(0, topK)
39
+ return rerankRows(topCandidates, queryText, topK)
40
+ }
41
+
42
+ export function vectorRemove(db, collection, id) {
43
+ db.prepare(`DELETE FROM vectors WHERE collection = ? AND id = ?`).run(collection, id)
44
+ }
45
+
46
+ export function vectorCount(db, collection) {
47
+ const row = db.prepare(`SELECT COUNT(*) as cnt FROM vectors WHERE collection = ?`).get(collection)
48
+ return row?.cnt || 0
49
+ }
50
+
51
+ function cosineSimilarity(a, b) {
52
+ let dot = 0
53
+ let normA = 0
54
+ let normB = 0
55
+ for (let i = 0; i < a.length; i++) {
56
+ dot += a[i] * b[i]
57
+ normA += a[i] * a[i]
58
+ normB += b[i] * b[i]
59
+ }
60
+ const denom = Math.sqrt(normA) * Math.sqrt(normB)
61
+ return denom === 0 ? 0 : dot / denom
62
+ }
63
+
64
+ function tokenize(value) {
65
+ if (typeof value !== 'string') return []
66
+ return value
67
+ .toLowerCase()
68
+ .split(/[^\p{L}\p{N}]+/gu)
69
+ .filter(Boolean)
70
+ }
71
+
72
+ function lexicalScore(queryText, candidateText) {
73
+ const queryTokens = new Set(tokenize(queryText))
74
+ if (queryTokens.size === 0) return 0
75
+ const candidateTokens = new Set(tokenize(candidateText))
76
+ if (candidateTokens.size === 0) return 0
77
+ let overlap = 0
78
+ for (const t of queryTokens) {
79
+ if (candidateTokens.has(t)) overlap++
80
+ }
81
+ return overlap / Math.sqrt(queryTokens.size * candidateTokens.size)
82
+ }
83
+
84
+ function rerankRows(rows, queryText, topK) {
85
+ const VECTOR_WEIGHT = 0.6
86
+ const LEXICAL_WEIGHT = 0.3
87
+ const PHRASE_BOOST = 0.1
88
+ const normalizedQuery = String(queryText || '')
89
+ .toLowerCase()
90
+ .trim()
91
+
92
+ return rows
93
+ .map((row) => {
94
+ const text = row.metadata?.text || row.metadata?.instructions || ''
95
+ const lexical = lexicalScore(queryText, text)
96
+ const vector = Math.max(0, row.score || 0)
97
+ const phraseMatch = normalizedQuery.length > 0 && text.toLowerCase().includes(normalizedQuery)
98
+ return {
99
+ ...row,
100
+ score: VECTOR_WEIGHT * vector + LEXICAL_WEIGHT * lexical + (phraseMatch ? PHRASE_BOOST : 0)
101
+ }
102
+ })
103
+ .sort((a, b) => b.score - a.score)
104
+ .slice(0, topK)
105
+ }
package/src/config.js DELETED
@@ -1,62 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
2
- import path from 'node:path'
3
-
4
- function resolveConfigPath(configPath) {
5
- const normalized = String(configPath || '').trim()
6
- if (!normalized) {
7
- throw new Error('A config path is required.')
8
- }
9
- return path.resolve(normalized)
10
- }
11
-
12
- function readConfigFile(configPath) {
13
- const resolvedPath = resolveConfigPath(configPath)
14
- if (!existsSync(resolvedPath)) {
15
- return {}
16
- }
17
-
18
- try {
19
- const raw = readFileSync(resolvedPath, 'utf8')
20
- if (!raw.trim()) return {}
21
- const parsed = JSON.parse(raw)
22
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
23
- return {}
24
- }
25
- return parsed
26
- } catch {
27
- return {}
28
- }
29
- }
30
-
31
- function writeConfigFile(configPath, value) {
32
- const resolvedPath = resolveConfigPath(configPath)
33
- mkdirSync(path.dirname(resolvedPath), { recursive: true })
34
- const tempPath = `${resolvedPath}.tmp`
35
- writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
36
- renameSync(tempPath, resolvedPath)
37
- }
38
-
39
- export function configGet(configPath, key) {
40
- const config = readConfigFile(configPath)
41
- return config[String(key)]
42
- }
43
-
44
- export function configSet(configPath, key, value) {
45
- const config = readConfigFile(configPath)
46
- config[String(key)] = value
47
- writeConfigFile(configPath, config)
48
- return value
49
- }
50
-
51
- export function configDelete(configPath, key) {
52
- const config = readConfigFile(configPath)
53
- const normalizedKey = String(key)
54
- const existed = Object.prototype.hasOwnProperty.call(config, normalizedKey)
55
- delete config[normalizedKey]
56
- writeConfigFile(configPath, config)
57
- return existed
58
- }
59
-
60
- export function configGetAll(configPath) {
61
- return { ...readConfigFile(configPath) }
62
- }
package/src/messages.js DELETED
@@ -1,213 +0,0 @@
1
- export const DEFAULT_CONVERSATION_ID = 'main'
2
-
3
- function getConversationId(conversationId) {
4
- const normalized = String(conversationId || '').trim()
5
- return normalized || DEFAULT_CONVERSATION_ID
6
- }
7
-
8
- function normalizeLimit(limit) {
9
- const parsed = Number.parseInt(limit, 10)
10
- return Number.isFinite(parsed) && parsed > 0 ? parsed : null
11
- }
12
-
13
- function normalizeBeforeId(beforeId) {
14
- const parsed = Number.parseInt(beforeId, 10)
15
- return Number.isFinite(parsed) && parsed > 0 ? parsed : null
16
- }
17
-
18
- function mapRow(row) {
19
- if (!row) return null
20
- return {
21
- id: row.id,
22
- conversationId: row.conversation_id,
23
- role: row.role,
24
- content: row.content,
25
- createdAt: row.created_at
26
- }
27
- }
28
-
29
- export function ensureConversation(db, conversationId = DEFAULT_CONVERSATION_ID) {
30
- const id = getConversationId(conversationId)
31
- const now = new Date().toISOString()
32
-
33
- db.prepare(
34
- `
35
- INSERT INTO conversations (id, created_at, updated_at)
36
- VALUES (?, ?, ?)
37
- ON CONFLICT(id) DO NOTHING
38
- `
39
- ).run(id, now, now)
40
-
41
- return db
42
- .prepare(
43
- `
44
- SELECT id, created_at, updated_at
45
- FROM conversations
46
- WHERE id = ?
47
- `
48
- )
49
- .get(id)
50
- }
51
-
52
- export function touchConversation(db, conversationId = DEFAULT_CONVERSATION_ID) {
53
- const id = getConversationId(conversationId)
54
- const now = new Date().toISOString()
55
-
56
- db.prepare(
57
- `
58
- INSERT INTO conversations (id, created_at, updated_at)
59
- VALUES (?, ?, ?)
60
- ON CONFLICT(id) DO UPDATE SET updated_at = excluded.updated_at
61
- `
62
- ).run(id, now, now)
63
-
64
- return db
65
- .prepare(
66
- `
67
- SELECT id, created_at, updated_at
68
- FROM conversations
69
- WHERE id = ?
70
- `
71
- )
72
- .get(id)
73
- }
74
-
75
- export function appendMessage(db, role, content, conversationId = DEFAULT_CONVERSATION_ID) {
76
- const id = getConversationId(conversationId)
77
- const now = new Date().toISOString()
78
- const normalizedRole = String(role || '').trim() || 'user'
79
- const normalizedContent = String(content ?? '')
80
-
81
- touchConversation(db, id)
82
-
83
- const result = db
84
- .prepare(
85
- `
86
- INSERT INTO messages (conversation_id, role, content, created_at)
87
- VALUES (?, ?, ?, ?)
88
- `
89
- )
90
- .run(id, normalizedRole, normalizedContent, now)
91
-
92
- return mapRow(
93
- db
94
- .prepare(
95
- `
96
- SELECT id, conversation_id, role, content, created_at
97
- FROM messages
98
- WHERE id = ?
99
- `
100
- )
101
- .get(result.lastInsertRowid)
102
- )
103
- }
104
-
105
- export function getMessages(db, conversationId = DEFAULT_CONVERSATION_ID, limit) {
106
- const id = getConversationId(conversationId)
107
- const normalizedLimit = normalizeLimit(limit)
108
-
109
- if (!normalizedLimit) {
110
- return db
111
- .prepare(
112
- `
113
- SELECT id, conversation_id, role, content, created_at
114
- FROM messages
115
- WHERE conversation_id = ?
116
- ORDER BY id ASC
117
- `
118
- )
119
- .all(id)
120
- .map(mapRow)
121
- }
122
-
123
- return db
124
- .prepare(
125
- `
126
- SELECT id, conversation_id, role, content, created_at
127
- FROM (
128
- SELECT id, conversation_id, role, content, created_at
129
- FROM messages
130
- WHERE conversation_id = ?
131
- ORDER BY id DESC
132
- LIMIT ?
133
- )
134
- ORDER BY id ASC
135
- `
136
- )
137
- .all(id, normalizedLimit)
138
- .map(mapRow)
139
- }
140
-
141
- export function getMessagesBeforeId(
142
- db,
143
- beforeId,
144
- conversationId = DEFAULT_CONVERSATION_ID,
145
- limit = 50
146
- ) {
147
- const id = getConversationId(conversationId)
148
- const normalizedBeforeId = normalizeBeforeId(beforeId)
149
- const normalizedLimit = normalizeLimit(limit) || 50
150
-
151
- if (!normalizedBeforeId) {
152
- return []
153
- }
154
-
155
- return db
156
- .prepare(
157
- `
158
- SELECT id, conversation_id, role, content, created_at
159
- FROM (
160
- SELECT id, conversation_id, role, content, created_at
161
- FROM messages
162
- WHERE conversation_id = ? AND id < ?
163
- ORDER BY id DESC
164
- LIMIT ?
165
- )
166
- ORDER BY id ASC
167
- `
168
- )
169
- .all(id, normalizedBeforeId, normalizedLimit)
170
- .map(mapRow)
171
- }
172
-
173
- export function clearMessages(db, conversationId = DEFAULT_CONVERSATION_ID) {
174
- const id = getConversationId(conversationId)
175
- ensureConversation(db, id)
176
- return db
177
- .prepare(
178
- `
179
- DELETE FROM messages
180
- WHERE conversation_id = ?
181
- `
182
- )
183
- .run(id)
184
- }
185
-
186
- export function saveSummaryCheckpoint(
187
- db,
188
- summary,
189
- checkpointId,
190
- conversationId = DEFAULT_CONVERSATION_ID
191
- ) {
192
- const id = getConversationId(conversationId)
193
- ensureConversation(db, id)
194
- db.prepare(
195
- `UPDATE conversations SET context_summary = ?, context_checkpoint_id = ?, updated_at = ? WHERE id = ?`
196
- ).run(summary, checkpointId, new Date().toISOString(), id)
197
- }
198
-
199
- export function loadSummaryCheckpoint(db, conversationId = DEFAULT_CONVERSATION_ID) {
200
- const id = getConversationId(conversationId)
201
- const row = db
202
- .prepare(`SELECT context_summary, context_checkpoint_id FROM conversations WHERE id = ?`)
203
- .get(id)
204
- if (!row || !row.context_summary) return null
205
- return { summary: row.context_summary, checkpointId: row.context_checkpoint_id }
206
- }
207
-
208
- export function clearSummaryCheckpoint(db, conversationId = DEFAULT_CONVERSATION_ID) {
209
- const id = getConversationId(conversationId)
210
- db.prepare(
211
- `UPDATE conversations SET context_summary = NULL, context_checkpoint_id = NULL, updated_at = ? WHERE id = ?`
212
- ).run(new Date().toISOString(), id)
213
- }
package/src/tasks.js DELETED
@@ -1,219 +0,0 @@
1
- function parseJson(value, fallback) {
2
- if (value === null || value === undefined || value === '') {
3
- return fallback
4
- }
5
-
6
- try {
7
- return JSON.parse(value)
8
- } catch {
9
- return fallback
10
- }
11
- }
12
-
13
- function stringifyJson(value, fallback = null) {
14
- if (value === null || value === undefined) {
15
- return fallback
16
- }
17
-
18
- try {
19
- return JSON.stringify(value)
20
- } catch {
21
- return fallback
22
- }
23
- }
24
-
25
- function mapTask(row) {
26
- if (!row) return null
27
-
28
- return {
29
- taskId: row.task_id,
30
- instructions: row.instructions,
31
- context: row.context,
32
- status: row.status,
33
- createdAt: row.created_at,
34
- updatedAt: row.updated_at,
35
- currentPlan: row.current_plan,
36
- message: row.message,
37
- result: row.result,
38
- completedAt: row.completed_at,
39
- failedAt: row.failed_at
40
- }
41
- }
42
-
43
- function mapActivity(row) {
44
- if (!row) return null
45
-
46
- return {
47
- id: row.id,
48
- taskId: row.task_id,
49
- type: row.type,
50
- name: row.name || null,
51
- rawResult: parseJson(row.raw_result, row.raw_result),
52
- timestamp: row.timestamp,
53
- data: parseJson(row.data, {})
54
- }
55
- }
56
-
57
- export function upsertTask(db, task) {
58
- const taskId = String(task?.taskId || '').trim()
59
- if (!taskId) {
60
- throw new Error('taskId is required.')
61
- }
62
-
63
- const createdAt = String(task?.createdAt || new Date().toISOString())
64
- const updatedAt = String(task?.updatedAt || createdAt)
65
-
66
- db.prepare(
67
- `
68
- INSERT INTO tasks (
69
- task_id,
70
- instructions,
71
- context,
72
- status,
73
- created_at,
74
- updated_at,
75
- current_plan,
76
- message,
77
- result,
78
- completed_at,
79
- failed_at
80
- )
81
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
82
- ON CONFLICT(task_id) DO UPDATE SET
83
- instructions = excluded.instructions,
84
- context = excluded.context,
85
- status = excluded.status,
86
- updated_at = excluded.updated_at,
87
- current_plan = excluded.current_plan,
88
- message = excluded.message,
89
- result = excluded.result,
90
- completed_at = excluded.completed_at,
91
- failed_at = excluded.failed_at
92
- `
93
- ).run(
94
- taskId,
95
- String(task?.instructions || ''),
96
- String(task?.context || ''),
97
- String(task?.status || 'queued'),
98
- createdAt,
99
- updatedAt,
100
- String(task?.currentPlan || ''),
101
- String(task?.message || ''),
102
- task?.result === null || task?.result === undefined ? null : String(task.result),
103
- String(task?.completedAt || ''),
104
- String(task?.failedAt || '')
105
- )
106
-
107
- return getTask(db, taskId)
108
- }
109
-
110
- export function getTask(db, taskId) {
111
- return mapTask(
112
- db
113
- .prepare(
114
- `
115
- SELECT
116
- task_id,
117
- instructions,
118
- context,
119
- status,
120
- created_at,
121
- updated_at,
122
- current_plan,
123
- message,
124
- result,
125
- completed_at,
126
- failed_at
127
- FROM tasks
128
- WHERE task_id = ?
129
- `
130
- )
131
- .get(String(taskId || '').trim())
132
- )
133
- }
134
-
135
- export function loadTasks(db) {
136
- return db
137
- .prepare(
138
- `
139
- SELECT
140
- task_id,
141
- instructions,
142
- context,
143
- status,
144
- created_at,
145
- updated_at,
146
- current_plan,
147
- message,
148
- result,
149
- completed_at,
150
- failed_at
151
- FROM tasks
152
- ORDER BY created_at DESC, task_id DESC
153
- `
154
- )
155
- .all()
156
- .map(mapTask)
157
- }
158
-
159
- export function appendTaskActivity(db, activity) {
160
- const id = String(activity?.id || '').trim()
161
- const taskId = String(activity?.taskId || '').trim()
162
- if (!id || !taskId) {
163
- throw new Error('Task activity requires id and taskId.')
164
- }
165
-
166
- db.prepare(
167
- `
168
- INSERT INTO task_activity (id, task_id, type, name, raw_result, timestamp, data)
169
- VALUES (?, ?, ?, ?, ?, ?, ?)
170
- ON CONFLICT(id) DO UPDATE SET
171
- task_id = excluded.task_id,
172
- type = excluded.type,
173
- name = excluded.name,
174
- raw_result = excluded.raw_result,
175
- timestamp = excluded.timestamp,
176
- data = excluded.data
177
- `
178
- ).run(
179
- id,
180
- taskId,
181
- String(activity?.type || ''),
182
- activity?.name ? String(activity.name) : null,
183
- stringifyJson(
184
- activity?.rawResult,
185
- activity?.rawResult === undefined ? null : String(activity.rawResult)
186
- ),
187
- String(activity?.timestamp || new Date().toISOString()),
188
- stringifyJson(activity?.data || {}, '{}')
189
- )
190
-
191
- return id
192
- }
193
-
194
- export function loadTaskActivity(db, taskId) {
195
- return db
196
- .prepare(
197
- `
198
- SELECT id, task_id, type, name, raw_result, timestamp, data
199
- FROM task_activity
200
- WHERE task_id = ?
201
- ORDER BY timestamp ASC, id ASC
202
- `
203
- )
204
- .all(String(taskId || '').trim())
205
- .map(mapActivity)
206
- }
207
-
208
- export function loadAllTaskActivity(db) {
209
- return db
210
- .prepare(
211
- `
212
- SELECT id, task_id, type, name, raw_result, timestamp, data
213
- FROM task_activity
214
- ORDER BY timestamp ASC, id ASC
215
- `
216
- )
217
- .all()
218
- .map(mapActivity)
219
- }