@gotcos/glasses-server 6.24.2 → 6.24.4

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.
@@ -5,9 +5,20 @@
5
5
  // server media index. Anything that persists or transmits an attachment
6
6
  // persists THIS shape (or just the id) and nothing else.
7
7
 
8
- export type MediaKind = 'user_photo' | 'traffic_frame' | 'generated_visual'
8
+ export type MediaKind = 'user_photo' | 'user_document' | 'user_video' | 'traffic_frame' | 'generated_visual'
9
9
 
10
- export type MediaMime = 'image/jpeg' | 'image/png'
10
+ export type MediaCategory = 'image' | 'document' | 'video'
11
+
12
+ export type MediaMime =
13
+ | 'image/jpeg'
14
+ | 'image/png'
15
+ | 'text/plain'
16
+ | 'text/markdown'
17
+ | 'text/csv'
18
+ | 'application/json'
19
+ | 'application/pdf'
20
+ | 'video/mp4'
21
+ | 'video/quicktime'
11
22
 
12
23
  export interface MediaAttachmentRef {
13
24
  id: string
@@ -16,6 +27,14 @@ export interface MediaAttachmentRef {
16
27
  width: number
17
28
  height: number
18
29
  createdAt: string
30
+ /** Additive discriminator. Legacy image refs deliberately omit it so
31
+ * durable request fingerprints remain byte-compatible. */
32
+ category?: MediaCategory
33
+ bytes?: number
34
+ durationMs?: number
35
+ frameCount?: number
36
+ textChars?: number
37
+ truncated?: boolean
19
38
  label?: string
20
39
  capturedAt?: string
21
40
  expiresAt?: string
@@ -36,8 +55,15 @@ export function isValidMediaId(id: unknown): id is string {
36
55
  return typeof id === 'string' && MEDIA_ID_RE.test(id)
37
56
  }
38
57
 
39
- const VALID_KINDS: ReadonlySet<string> = new Set(['user_photo', 'traffic_frame', 'generated_visual'])
40
- const VALID_MIMES: ReadonlySet<string> = new Set(['image/jpeg', 'image/png'])
58
+ const VALID_KINDS: ReadonlySet<string> = new Set([
59
+ 'user_photo', 'user_document', 'user_video', 'traffic_frame', 'generated_visual',
60
+ ])
61
+ const IMAGE_MIMES: ReadonlySet<string> = new Set(['image/jpeg', 'image/png'])
62
+ const DOCUMENT_MIMES: ReadonlySet<string> = new Set([
63
+ 'text/plain', 'text/markdown', 'text/csv', 'application/json', 'application/pdf',
64
+ ])
65
+ const VIDEO_MIMES: ReadonlySet<string> = new Set(['video/mp4', 'video/quicktime'])
66
+ const VALID_MIMES: ReadonlySet<string> = new Set([...IMAGE_MIMES, ...DOCUMENT_MIMES, ...VIDEO_MIMES])
41
67
  const MAX_LABEL_LEN = 120
42
68
  // ISO-8601 subset — what `new Date().toISOString()` emits.
43
69
  const ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/
@@ -62,6 +88,15 @@ export function parseMediaAttachmentRef(raw: unknown): MediaAttachmentRef | null
62
88
  if (typeof r.mime !== 'string' || !VALID_MIMES.has(r.mime)) return null
63
89
  if (!isDimension(r.width) || !isDimension(r.height)) return null
64
90
  if (!isIsoTimestamp(r.createdAt)) return null
91
+ const inferredCategory = r.kind === 'user_document'
92
+ ? 'document'
93
+ : r.kind === 'user_video'
94
+ ? 'video'
95
+ : 'image'
96
+ if ((inferredCategory === 'image' && !IMAGE_MIMES.has(r.mime))
97
+ || (inferredCategory === 'document' && !DOCUMENT_MIMES.has(r.mime))
98
+ || (inferredCategory === 'video' && !VIDEO_MIMES.has(r.mime))) return null
99
+ if (r.category !== undefined && r.category !== inferredCategory) return null
65
100
  const ref: MediaAttachmentRef = {
66
101
  id: r.id,
67
102
  kind: r.kind as MediaKind,
@@ -70,6 +105,22 @@ export function parseMediaAttachmentRef(raw: unknown): MediaAttachmentRef | null
70
105
  height: r.height,
71
106
  createdAt: r.createdAt,
72
107
  }
108
+ // Do not add category to legacy image refs that never carried it. Their
109
+ // canonical JSON is part of persisted durable-query fingerprints.
110
+ if (r.category === inferredCategory) ref.category = inferredCategory
111
+ if (typeof r.bytes === 'number' && Number.isSafeInteger(r.bytes) && r.bytes >= 0 && r.bytes <= 64 * 1024 * 1024) {
112
+ ref.bytes = r.bytes
113
+ }
114
+ if (typeof r.durationMs === 'number' && Number.isSafeInteger(r.durationMs) && r.durationMs >= 0 && r.durationMs <= 60 * 60_000) {
115
+ ref.durationMs = r.durationMs
116
+ }
117
+ if (typeof r.frameCount === 'number' && Number.isSafeInteger(r.frameCount) && r.frameCount >= 0 && r.frameCount <= 8) {
118
+ ref.frameCount = r.frameCount
119
+ }
120
+ if (typeof r.textChars === 'number' && Number.isSafeInteger(r.textChars) && r.textChars >= 0 && r.textChars <= 100_000) {
121
+ ref.textChars = r.textChars
122
+ }
123
+ if (r.truncated === true) ref.truncated = true
73
124
  if (typeof r.label === 'string' && r.label.length > 0) {
74
125
  ref.label = r.label.slice(0, MAX_LABEL_LEN)
75
126
  }
@@ -78,6 +129,35 @@ export function parseMediaAttachmentRef(raw: unknown): MediaAttachmentRef | null
78
129
  return ref
79
130
  }
80
131
 
132
+ export function mediaCategoryOf(ref: Pick<MediaAttachmentRef, 'kind' | 'category'>): MediaCategory {
133
+ if (ref.category) return ref.category
134
+ if (ref.kind === 'user_document') return 'document'
135
+ if (ref.kind === 'user_video') return 'video'
136
+ return 'image'
137
+ }
138
+
139
+ export function isImageAttachmentRef(ref: MediaAttachmentRef): boolean {
140
+ return mediaCategoryOf(ref) === 'image'
141
+ }
142
+
143
+ export function attachmentHistoryPrefix(refs: readonly MediaAttachmentRef[]): string {
144
+ if (refs.length === 0) return ''
145
+ if (refs.length > 1) return `[${refs.length} Attachments]`
146
+ const category = mediaCategoryOf(refs[0])
147
+ return category === 'image' ? '[Photo]' : category === 'video' ? '[Video]' : '[File]'
148
+ }
149
+
150
+ export function defaultAttachmentRequest(refs: readonly MediaAttachmentRef[]): string {
151
+ if (refs.length === 0) return ''
152
+ const categories = new Set(refs.map(mediaCategoryOf))
153
+ if (categories.size > 1 || refs.length > 1) return 'Review these attachments.'
154
+ return categories.has('document')
155
+ ? 'Summarize this file.'
156
+ : categories.has('video')
157
+ ? 'Review this video.'
158
+ : 'What do you see?'
159
+ }
160
+
81
161
  /** Validate an untrusted array of refs, dropping only the invalid entries
82
162
  * (a bad ref must never take the whole conversation record with it). */
83
163
  export function parseMediaAttachmentRefs(raw: unknown): MediaAttachmentRef[] {