@qvac/core 0.1.0 → 0.1.1
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,28 @@
|
|
|
1
|
+
// Hand-written declarations for the generated schema/helpers.js. assistant's own
|
|
2
|
+
// tsconfig uses allowJs + checkJs:false, but strict consumers (app-runtime,
|
|
3
|
+
// mobile) without allowJs need these types to import the module without TS7016.
|
|
4
|
+
// The `*Map` helpers are hyperdb map-reduce emitters: they return an array of
|
|
5
|
+
// plain record rows (e.g. `[{ chatId, type, seq }]`), never a Map. Rows stay
|
|
6
|
+
// `Record<string, any>` to match the previously-inferred shape and keep existing
|
|
7
|
+
// .ts callers permissive.
|
|
8
|
+
export function chunkTypeOrdinal(type: string): number
|
|
9
|
+
export function chunksByTypeMap(record: any): ReadonlyArray<Record<string, any>>
|
|
10
|
+
export function activeAgentsMap(record: any): ReadonlyArray<Record<string, any>>
|
|
11
|
+
export function activeChatsMap(record: any): ReadonlyArray<Record<string, any>>
|
|
12
|
+
export function activeChatsByAgentMap(record: any): ReadonlyArray<Record<string, any>>
|
|
13
|
+
export function activeChatGroupsMap(record: any): ReadonlyArray<Record<string, any>>
|
|
14
|
+
export function activeKnowledgeByChatMap(record: any): ReadonlyArray<Record<string, any>>
|
|
15
|
+
export function activeToolConfigsByAgentMap(record: any): ReadonlyArray<Record<string, any>>
|
|
16
|
+
export function toolActivityByAgentMap(record: any): ReadonlyArray<Record<string, any>>
|
|
17
|
+
export function runRequestsMap(record: any): ReadonlyArray<Record<string, any>>
|
|
18
|
+
export const MEDIA_KINDS: readonly ['audio', 'image', 'video', 'other']
|
|
19
|
+
export const AUDIO_EXTENSIONS: readonly string[]
|
|
20
|
+
export function mediaKindOf(
|
|
21
|
+
mimeType?: string,
|
|
22
|
+
fileName?: string
|
|
23
|
+
): 'audio' | 'image' | 'video' | 'other'
|
|
24
|
+
export function mediaKindOrdinal(kind: string): number
|
|
25
|
+
export function attachmentsByMediaKindMap(record: any): ReadonlyArray<Record<string, any>>
|
|
26
|
+
export function attachmentsByMediaKindOriginMap(record: any): ReadonlyArray<Record<string, any>>
|
|
27
|
+
export function historyChunksMap(record: any): ReadonlyArray<Record<string, any>>
|
|
28
|
+
export function liveWritersByMostRecentMap(record: any): ReadonlyArray<Record<string, any>>
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// imported by the generated spec at runtime (dbns.require embeds a relative path) — stays plain .js
|
|
2
|
+
const historyChunkTypes = ['user-message', 'run-request', 'content', 'attachment', 'compaction']
|
|
3
|
+
|
|
4
|
+
// index keys hold the ordinal, kept in sync by hand with CHUNK_TYPES order in content.ts
|
|
5
|
+
export function chunkTypeOrdinal(type) {
|
|
6
|
+
switch (type) {
|
|
7
|
+
case 'user-message':
|
|
8
|
+
return 1
|
|
9
|
+
case 'content':
|
|
10
|
+
return 2
|
|
11
|
+
case 'thinking':
|
|
12
|
+
return 3
|
|
13
|
+
case 'tool-call':
|
|
14
|
+
return 4
|
|
15
|
+
case 'tool-result':
|
|
16
|
+
return 5
|
|
17
|
+
case 'attachment':
|
|
18
|
+
return 6
|
|
19
|
+
case 'approval-request':
|
|
20
|
+
return 7
|
|
21
|
+
case 'run-status':
|
|
22
|
+
return 8
|
|
23
|
+
case 'run-request':
|
|
24
|
+
return 9
|
|
25
|
+
case 'sources':
|
|
26
|
+
return 10
|
|
27
|
+
case 'compaction':
|
|
28
|
+
return 11
|
|
29
|
+
default:
|
|
30
|
+
throw new Error('Unknown chunk type: ' + type)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function chunksByTypeMap(record) {
|
|
35
|
+
if (record.type === undefined) return []
|
|
36
|
+
return [{ chatId: record.chatId, type: chunkTypeOrdinal(record.type), seq: record.seq }]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function activeAgentsMap(record) {
|
|
40
|
+
if (record.deletedAt) return []
|
|
41
|
+
return [{ createdAt: record.createdAt }]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function activeChatsMap(record) {
|
|
45
|
+
if (record.deletedAt) return []
|
|
46
|
+
return [{ createdAt: record.createdAt }]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function activeChatGroupsMap(record) {
|
|
50
|
+
if (record.deletedAt) return []
|
|
51
|
+
return [{ createdAt: record.createdAt }]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function activeChatsByAgentMap(record) {
|
|
55
|
+
if (record.deletedAt) return []
|
|
56
|
+
return [{ agentId: record.agentId, createdAt: record.createdAt }]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function activeKnowledgeByChatMap(record) {
|
|
60
|
+
if (record.deletedAt || !record.chatId) return []
|
|
61
|
+
return [{ chatId: record.chatId }]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function activeToolConfigsByAgentMap(record) {
|
|
65
|
+
if (record.deletedAt) return []
|
|
66
|
+
return [{ agentId: record.agentId }]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// append-only tool-call log keyed by agent then apply-assigned seq — one range query lists an
|
|
70
|
+
// agent's whole activity across every chat and device, in a peer-consistent order (seq, not wall
|
|
71
|
+
// clock). No tombstone filter: activity is immutable history.
|
|
72
|
+
export function toolActivityByAgentMap(record) {
|
|
73
|
+
if (!record.agentId) return []
|
|
74
|
+
return [{ agentId: record.agentId, seq: record.seq }]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function runRequestsMap(record) {
|
|
78
|
+
if (record.type !== 'run-request') return []
|
|
79
|
+
return [{ chatId: record.chatId, seq: record.seq }]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// media kinds for the attachments-by-media-kind index. The ordinal rides in the key as a plain
|
|
83
|
+
// uint (same pattern as chunksByTypeMap); MEDIA_KINDS is the name↔ordinal source of truth, and
|
|
84
|
+
// the wire media-kind enum in engine.ts is registered from this same array so the two never drift.
|
|
85
|
+
export const MEDIA_KINDS = ['audio', 'image', 'video', 'other']
|
|
86
|
+
|
|
87
|
+
// `.mp4` is deliberately audio, not video: audio recordings ship in the mp4 container and are
|
|
88
|
+
// transcribed from their audio track (parity with isAudioAttachment). audio-attachment.ts derives
|
|
89
|
+
// isAudioAttachment from mediaKindOf, so this list is the one source of truth for "what is audio".
|
|
90
|
+
export const AUDIO_EXTENSIONS = ['.m4a', '.mp3', '.wav', '.ogg', '.flac', '.aac', '.mp4']
|
|
91
|
+
const IMAGE_EXTENSIONS = [
|
|
92
|
+
'.png',
|
|
93
|
+
'.jpg',
|
|
94
|
+
'.jpeg',
|
|
95
|
+
'.gif',
|
|
96
|
+
'.webp',
|
|
97
|
+
'.bmp',
|
|
98
|
+
'.svg',
|
|
99
|
+
'.heic',
|
|
100
|
+
'.heif',
|
|
101
|
+
'.avif'
|
|
102
|
+
]
|
|
103
|
+
const VIDEO_EXTENSIONS = ['.mov', '.webm', '.mkv', '.avi', '.m4v']
|
|
104
|
+
|
|
105
|
+
function endsWithAny(name, extensions) {
|
|
106
|
+
return extensions.some((extension) => name.endsWith(extension))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// classify an attachment by mime first, then filename extension. Broad by design so a generated
|
|
110
|
+
// `audio/wav` and a user `recording.m4a` both land under 'audio'.
|
|
111
|
+
export function mediaKindOf(mimeType, fileName) {
|
|
112
|
+
const mime = (mimeType || '').toLowerCase()
|
|
113
|
+
const name = (fileName || '').toLowerCase()
|
|
114
|
+
if (mime.startsWith('audio/') || endsWithAny(name, AUDIO_EXTENSIONS)) return 'audio'
|
|
115
|
+
if (mime.startsWith('image/') || endsWithAny(name, IMAGE_EXTENSIONS)) return 'image'
|
|
116
|
+
if (mime.startsWith('video/') || endsWithAny(name, VIDEO_EXTENSIONS)) return 'video'
|
|
117
|
+
return 'other'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function mediaKindOrdinal(kind) {
|
|
121
|
+
const ordinal = MEDIA_KINDS.indexOf(kind)
|
|
122
|
+
if (ordinal === -1) throw new Error('Unknown media kind: ' + kind)
|
|
123
|
+
return ordinal + 1
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// every non-deleted attachment chunk, keyed by media kind then createdAt — NOT chatId-prefixed,
|
|
127
|
+
// so one range query lists all audio (uploaded or generated) across every chat in the mesh. Drops
|
|
128
|
+
// non-attachment chunks and tombstoned attachments so a gallery never scans-and-filters.
|
|
129
|
+
export function attachmentsByMediaKindMap(record) {
|
|
130
|
+
if (record.type !== 'attachment') return []
|
|
131
|
+
const attachment = record.attachment
|
|
132
|
+
if (!attachment || attachment.deletedAt) return []
|
|
133
|
+
return [
|
|
134
|
+
{
|
|
135
|
+
mediaKind: mediaKindOrdinal(mediaKindOf(attachment.mimeType, attachment.fileName)),
|
|
136
|
+
createdAt: record.createdAt
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// generated tool output always carries a runId; a user upload never does (attachment-knowledge.ts
|
|
142
|
+
// only routes runId-less uploads to knowledge). So runId presence IS the origin, kept out of the
|
|
143
|
+
// key as a plain 0/1 uint the way mediaKind is.
|
|
144
|
+
function attachmentGenerated(record) {
|
|
145
|
+
return record.runId ? 1 : 0
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// the same attachments as attachmentsByMediaKindMap, but with origin (generated vs uploaded) folded
|
|
149
|
+
// into the key so "only generated audio" / "only uploaded audio" is a range query, not a post-filter.
|
|
150
|
+
export function attachmentsByMediaKindOriginMap(record) {
|
|
151
|
+
if (record.type !== 'attachment') return []
|
|
152
|
+
const attachment = record.attachment
|
|
153
|
+
if (!attachment || attachment.deletedAt) return []
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
mediaKind: mediaKindOrdinal(mediaKindOf(attachment.mimeType, attachment.fileName)),
|
|
157
|
+
generated: attachmentGenerated(record),
|
|
158
|
+
createdAt: record.createdAt
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function devicesByWriterKeyMap(record) {
|
|
164
|
+
if (!record.writerKey) return []
|
|
165
|
+
return [{ writerKey: record.writerKey }]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function historyChunksMap(record) {
|
|
169
|
+
if (!historyChunkTypes.includes(record.type)) return []
|
|
170
|
+
return [{ chatId: record.chatId, seq: record.seq }]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function liveWritersByMostRecentMap(record) {
|
|
174
|
+
if (record.revoked) return []
|
|
175
|
+
return [{ clock: record.clock }]
|
|
176
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qvac/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Peer-to-peer AI across your devices",
|
|
6
6
|
"exports": {
|
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
"default": "./dist-lib/client.js"
|
|
70
70
|
},
|
|
71
71
|
"./capabilities": "./dist-lib/spec/capabilities.d.ts",
|
|
72
|
+
"./wire-codec": {
|
|
73
|
+
"types": "./dist-lib/spec/hrpc/messages.d.ts",
|
|
74
|
+
"default": "./dist-lib/spec/hrpc/messages.js"
|
|
75
|
+
},
|
|
72
76
|
"./log": {
|
|
73
77
|
"types": "./dist-lib/lib/log.d.ts",
|
|
74
78
|
"default": "./dist-lib/lib/log.js"
|