@gotcos/glasses-server 6.2.1 → 6.5.0
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/.env.example +18 -7
- package/CHANGELOG.md +116 -0
- package/README.md +26 -8
- package/bin/cli.cjs +22 -10
- package/package.json +18 -6
- package/server/bin/cos-output-image-publisher.mjs +324 -0
- package/server/bootstrap.ts +16 -0
- package/server/index.ts +65 -15
- package/server/lib/activity-preview.ts +168 -0
- package/server/lib/archive.ts +27 -9
- package/server/lib/claude-bridge.ts +215 -60
- package/server/lib/claude-run-ledger.ts +7 -2
- package/server/lib/codex-bridge.ts +186 -71
- package/server/lib/codex-engine-sessions.ts +24 -2
- package/server/lib/codex-model-catalog.ts +450 -0
- package/server/lib/codex-run-ledger.ts +20 -4
- package/server/lib/conversation.ts +64 -2
- package/server/lib/image-safety.ts +458 -0
- package/server/lib/listener-startup.ts +29 -0
- package/server/lib/media-store.ts +833 -0
- package/server/lib/model-image-input.ts +27 -0
- package/server/lib/model-router.ts +67 -8
- package/server/lib/query-attachments.ts +132 -0
- package/server/lib/run-output-images.ts +442 -0
- package/server/lib/server-instance-lock.ts +122 -0
- package/server/routes/archive.ts +79 -0
- package/server/routes/health.ts +17 -1
- package/server/routes/media.ts +285 -0
- package/server/routes/message-ref.ts +202 -0
- package/server/routes/openai-compat.ts +44 -11
- package/server/routes/query.ts +51 -16
- package/server/routes/sessions.ts +299 -0
- package/shared/media-attachment.ts +126 -0
- package/shared/model-preference.ts +140 -17
|
@@ -1,42 +1,159 @@
|
|
|
1
|
-
export type ClaudeModelPreference = 'opus' | 'sonnet' | 'haiku'
|
|
2
|
-
|
|
1
|
+
export type ClaudeModelPreference = 'opus' | 'fable' | 'sonnet' | 'haiku'
|
|
2
|
+
|
|
3
|
+
// Stable app-level slots. Concrete GPT model ids resolve at runtime from the
|
|
4
|
+
// local Codex CLI catalog, so a shipped glasses app keeps tracking new releases.
|
|
5
|
+
export type CodexModelPreference = 'codex-frontier' | 'codex-balanced'
|
|
3
6
|
export type ModelPreference = ClaudeModelPreference | CodexModelPreference
|
|
4
7
|
|
|
8
|
+
// Preserve the public server's established fast, broadly available default.
|
|
5
9
|
export const DEFAULT_MODEL = 'sonnet' as const
|
|
6
|
-
export const
|
|
10
|
+
export const CODEX_FRONTIER_MODEL: CodexModelPreference = 'codex-frontier'
|
|
11
|
+
export const CODEX_BALANCED_MODEL: CodexModelPreference = 'codex-balanced'
|
|
12
|
+
// Backward-compatible export for callers that predate the two-slot catalog.
|
|
13
|
+
export const CODEX_HIGH_MODEL: CodexModelPreference = CODEX_FRONTIER_MODEL
|
|
14
|
+
// Existing 6.1–6.3 installs may pin the legacy codex-high slot. Frontier is its
|
|
15
|
+
// migration target; Balanced remains auto-catalog even when this override is set.
|
|
16
|
+
export const CODEX_MODEL_ID = process.env.COS_CODEX_MODEL?.trim() ?? ''
|
|
17
|
+
|
|
18
|
+
export type CodexReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'ultra'
|
|
19
|
+
const CODEX_REASONING_EFFORT_SET = new Set<CodexReasoningEffort>(['low', 'medium', 'high', 'xhigh', 'max', 'ultra'])
|
|
20
|
+
|
|
21
|
+
export function resolveConfiguredCodexReasoningEffort(): CodexReasoningEffort {
|
|
22
|
+
const raw = process.env.COS_CODEX_REASONING_EFFORT?.trim().toLowerCase()
|
|
23
|
+
return raw && CODEX_REASONING_EFFORT_SET.has(raw as CodexReasoningEffort)
|
|
24
|
+
? raw as CodexReasoningEffort
|
|
25
|
+
: 'high'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const CODEX_HIGH_REASONING_EFFORT: CodexReasoningEffort = resolveConfiguredCodexReasoningEffort()
|
|
29
|
+
|
|
30
|
+
export const MODEL_OPTIONS: ModelPreference[] = [
|
|
31
|
+
'opus',
|
|
32
|
+
'fable',
|
|
33
|
+
'sonnet',
|
|
34
|
+
CODEX_FRONTIER_MODEL,
|
|
35
|
+
CODEX_BALANCED_MODEL,
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
const MODEL_SET = new Set<ModelPreference>([
|
|
39
|
+
'opus',
|
|
40
|
+
'fable',
|
|
41
|
+
'sonnet',
|
|
42
|
+
'haiku',
|
|
43
|
+
CODEX_FRONTIER_MODEL,
|
|
44
|
+
CODEX_BALANCED_MODEL,
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
// Bare Claude tier aliases resolve to the newest model in that tier at spawn.
|
|
48
|
+
// The [1m] suffix keeps the large-context contract without pinning a version.
|
|
49
|
+
export const CLAUDE_CLI_MODEL_ID: Record<ClaudeModelPreference, string> = {
|
|
50
|
+
opus: 'opus[1m]',
|
|
51
|
+
fable: 'fable[1m]',
|
|
52
|
+
sonnet: 'sonnet[1m]',
|
|
53
|
+
haiku: 'haiku',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function resolveClaudeCliModelId(model: ClaudeModelPreference): string {
|
|
57
|
+
return CLAUDE_CLI_MODEL_ID[model] ?? CLAUDE_CLI_MODEL_ID[DEFAULT_MODEL]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Format a concrete Claude CLI model id for diagnostics. */
|
|
61
|
+
export function formatResolvedModelDisplay(cliModelId: string | undefined): string {
|
|
62
|
+
if (!cliModelId) return ''
|
|
63
|
+
const oneM = cliModelId.includes('[1m]')
|
|
64
|
+
const bare = cliModelId.replace('[1m]', '')
|
|
65
|
+
const match = /^claude-([a-z]+)-(\d+(?:-\d+)*)$/.exec(bare)
|
|
66
|
+
if (!match) return cliModelId
|
|
67
|
+
const family = match[1].charAt(0).toUpperCase() + match[1].slice(1)
|
|
68
|
+
const version = match[2].split('-').join('.')
|
|
69
|
+
return `${family} ${version}${oneM ? ' (1M)' : ''}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type EffortPreference = 'high' | 'xhigh' | 'max' | 'ultracode'
|
|
73
|
+
export const DEFAULT_EFFORT: EffortPreference = 'high'
|
|
74
|
+
export const EFFORT_OPTIONS: EffortPreference[] = ['high', 'xhigh', 'max', 'ultracode']
|
|
75
|
+
const EFFORT_SET = new Set<EffortPreference>(EFFORT_OPTIONS)
|
|
76
|
+
export const ULTRACODE_KEYWORD = 'ultracode'
|
|
77
|
+
|
|
78
|
+
export function isEffortPreference(value: unknown): value is EffortPreference {
|
|
79
|
+
return typeof value === 'string' && EFFORT_SET.has(value as EffortPreference)
|
|
80
|
+
}
|
|
7
81
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export
|
|
82
|
+
export function normalizeEffortPreference(value: unknown): EffortPreference | undefined {
|
|
83
|
+
return isEffortPreference(value) ? value : undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Map the UI-only ultracode choice to a Claude CLI effort flag. */
|
|
87
|
+
export function resolveCliEffortFlag(effort: EffortPreference): 'high' | 'xhigh' | 'max' {
|
|
88
|
+
return effort === 'ultracode' ? 'xhigh' : effort
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Codex Fast mode is emitted only when the selected live model advertises it.
|
|
92
|
+
export const CODEX_SERVICE_TIER = 'priority'
|
|
14
93
|
|
|
15
|
-
export
|
|
94
|
+
export function resolveCodexReasoningEffort(effort: EffortPreference | undefined): CodexReasoningEffort {
|
|
95
|
+
if (effort === 'ultracode') return 'ultra'
|
|
96
|
+
if (effort === 'max') return 'max'
|
|
97
|
+
if (effort === 'xhigh') return 'xhigh'
|
|
98
|
+
return 'high'
|
|
99
|
+
}
|
|
16
100
|
|
|
17
|
-
|
|
101
|
+
export function effortLabel(effort: EffortPreference): string {
|
|
102
|
+
switch (effort) {
|
|
103
|
+
case 'xhigh': return 'Extra High'
|
|
104
|
+
case 'max': return 'Max'
|
|
105
|
+
case 'ultracode': return 'Ultracode'
|
|
106
|
+
case 'high':
|
|
107
|
+
default:
|
|
108
|
+
return 'High'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
18
111
|
|
|
19
112
|
export function isModelPreference(value: unknown): value is ModelPreference {
|
|
20
113
|
return typeof value === 'string' && MODEL_SET.has(value as ModelPreference)
|
|
21
114
|
}
|
|
22
115
|
|
|
23
116
|
export function normalizeModelPreference(value: unknown): ModelPreference | undefined {
|
|
117
|
+
// Existing installs persist codex-high. Migrate it to the live frontier slot.
|
|
118
|
+
if (value === 'codex-high') return CODEX_FRONTIER_MODEL
|
|
24
119
|
return isModelPreference(value) ? value : undefined
|
|
25
120
|
}
|
|
26
121
|
|
|
27
122
|
export function isClaudeModel(model: ModelPreference): model is ClaudeModelPreference {
|
|
28
|
-
return model === 'opus' || model === 'sonnet' || model === 'haiku'
|
|
123
|
+
return model === 'opus' || model === 'fable' || model === 'sonnet' || model === 'haiku'
|
|
29
124
|
}
|
|
30
125
|
|
|
31
126
|
export function isCodexModel(model: ModelPreference): model is CodexModelPreference {
|
|
32
|
-
return model ===
|
|
127
|
+
return model === CODEX_FRONTIER_MODEL || model === CODEX_BALANCED_MODEL
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface RuntimeCodexModelLabel {
|
|
131
|
+
preference: CodexModelPreference
|
|
132
|
+
displayName: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const runtimeCodexLabels: Partial<Record<CodexModelPreference, string>> = {}
|
|
136
|
+
|
|
137
|
+
export function setRuntimeCodexModelLabels(options: RuntimeCodexModelLabel[]): void {
|
|
138
|
+
for (const option of options) {
|
|
139
|
+
if (!isCodexModel(option.preference)) continue
|
|
140
|
+
const label = typeof option.displayName === 'string' ? option.displayName.trim() : ''
|
|
141
|
+
if (label) runtimeCodexLabels[option.preference] = label
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function resetRuntimeCodexModelLabels(): void {
|
|
146
|
+
delete runtimeCodexLabels[CODEX_FRONTIER_MODEL]
|
|
147
|
+
delete runtimeCodexLabels[CODEX_BALANCED_MODEL]
|
|
33
148
|
}
|
|
34
149
|
|
|
35
150
|
export function modelLabel(model: ModelPreference): string {
|
|
36
151
|
switch (model) {
|
|
152
|
+
case 'fable': return 'Fable'
|
|
37
153
|
case 'sonnet': return 'Sonnet'
|
|
38
154
|
case 'haiku': return 'Haiku'
|
|
39
|
-
case 'codex-
|
|
155
|
+
case 'codex-frontier': return runtimeCodexLabels[model] ?? 'GPT Frontier'
|
|
156
|
+
case 'codex-balanced': return runtimeCodexLabels[model] ?? 'GPT Balanced'
|
|
40
157
|
case 'opus':
|
|
41
158
|
default:
|
|
42
159
|
return 'Opus'
|
|
@@ -45,9 +162,11 @@ export function modelLabel(model: ModelPreference): string {
|
|
|
45
162
|
|
|
46
163
|
export function modelShortLabel(model: ModelPreference): string {
|
|
47
164
|
switch (model) {
|
|
165
|
+
case 'fable': return 'Fable'
|
|
48
166
|
case 'sonnet': return 'Sonnet'
|
|
49
167
|
case 'haiku': return 'Haiku'
|
|
50
|
-
case 'codex-
|
|
168
|
+
case 'codex-frontier': return 'GPT Max'
|
|
169
|
+
case 'codex-balanced': return 'GPT Bal'
|
|
51
170
|
case 'opus':
|
|
52
171
|
default:
|
|
53
172
|
return 'Opus'
|
|
@@ -56,9 +175,11 @@ export function modelShortLabel(model: ModelPreference): string {
|
|
|
56
175
|
|
|
57
176
|
export function modelButtonLabel(model: ModelPreference): string {
|
|
58
177
|
switch (model) {
|
|
178
|
+
case 'fable': return 'FABLE'
|
|
59
179
|
case 'sonnet': return 'SNNT'
|
|
60
180
|
case 'haiku': return 'HAIKU'
|
|
61
|
-
case 'codex-
|
|
181
|
+
case 'codex-frontier': return 'GPT MAX'
|
|
182
|
+
case 'codex-balanced': return 'GPT BAL'
|
|
62
183
|
case 'opus':
|
|
63
184
|
default:
|
|
64
185
|
return 'OPUS'
|
|
@@ -67,9 +188,11 @@ export function modelButtonLabel(model: ModelPreference): string {
|
|
|
67
188
|
|
|
68
189
|
export function modelTag(model: ModelPreference): string {
|
|
69
190
|
switch (model) {
|
|
191
|
+
case 'fable': return 'F'
|
|
70
192
|
case 'sonnet': return 'S'
|
|
71
193
|
case 'haiku': return 'H'
|
|
72
|
-
case 'codex-
|
|
194
|
+
case 'codex-frontier': return 'GF'
|
|
195
|
+
case 'codex-balanced': return 'GB'
|
|
73
196
|
case 'opus':
|
|
74
197
|
default:
|
|
75
198
|
return 'O'
|