@gotcos/glasses-server 6.16.0 → 6.16.2
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 +7 -0
- package/CHANGELOG.md +19 -0
- package/README.md +29 -9
- package/bin/cli.cjs +71 -5
- package/package.json +8 -3
- package/server/lib/claude-bridge.ts +14 -1
- package/server/lib/cli-debug-view.ts +31 -4
- package/server/lib/cos-operations-meetings.ts +365 -0
- package/server/lib/cursor-bridge.ts +726 -0
- package/server/lib/cursor-engine-sessions.ts +162 -0
- package/server/lib/cursor-model-catalog.ts +288 -0
- package/server/lib/cursor-run-ledger.ts +300 -0
- package/server/lib/hallucination-filter.ts +1 -1
- package/server/lib/model-router.ts +26 -1
- package/server/lib/provider-terminal-error.ts +1 -1
- package/server/lib/query-job-coordinator.ts +1 -0
- package/server/lib/query-job-runtime.ts +8 -2
- package/server/lib/query-job-store.ts +11 -3
- package/server/lib/query-job-types.ts +2 -1
- package/server/lib/speaker-trainer.ts +1 -1
- package/server/lib/token-audit.ts +11 -1
- package/server/models/silero_vad.onnx +0 -0
- package/server/routes/cli-debug.ts +8 -0
- package/server/routes/health.ts +56 -4
- package/server/routes/meetings.ts +43 -3
- package/server/routes/openai-compat.ts +28 -4
- package/shared/model-preference.ts +50 -2
package/server/routes/health.ts
CHANGED
|
@@ -19,6 +19,12 @@ import {
|
|
|
19
19
|
getCodexModelCatalog,
|
|
20
20
|
getCodexModelCatalogSnapshot,
|
|
21
21
|
} from '../lib/codex-model-catalog.js'
|
|
22
|
+
import {
|
|
23
|
+
getCursorModelCatalog,
|
|
24
|
+
getCursorModelCatalogSnapshot,
|
|
25
|
+
isCursorProviderReady,
|
|
26
|
+
resolveAgentBinary,
|
|
27
|
+
} from '../lib/cursor-model-catalog.js'
|
|
22
28
|
import { isMediaProcessingReady } from '../lib/image-safety.js'
|
|
23
29
|
import { G2_LENS_VARIANT_CAPABILITY } from '../lib/media-store.js'
|
|
24
30
|
import { durableQueryJobsCapability } from '../lib/query-job-feature.js'
|
|
@@ -70,6 +76,7 @@ healthRouter.get('/health', async (_req, res) => {
|
|
|
70
76
|
python: 'unknown',
|
|
71
77
|
claude: 'unknown',
|
|
72
78
|
codex: 'unknown',
|
|
79
|
+
cursor: 'unknown',
|
|
73
80
|
uptime_seconds: Math.floor((Date.now() - serverMetrics.startedAt) / 1000),
|
|
74
81
|
request_count: serverMetrics.requestCount,
|
|
75
82
|
}
|
|
@@ -77,6 +84,7 @@ healthRouter.get('/health', async (_req, res) => {
|
|
|
77
84
|
// Feature detection flags
|
|
78
85
|
let claudeAvailable = false
|
|
79
86
|
let codexAvailable = false
|
|
87
|
+
let cursorAvailable = false
|
|
80
88
|
|
|
81
89
|
// Check Python venv (COS mode only)
|
|
82
90
|
if (PYTHON_BIN) {
|
|
@@ -126,6 +134,36 @@ healthRouter.get('/health', async (_req, res) => {
|
|
|
126
134
|
checks.codex = 'error'
|
|
127
135
|
}
|
|
128
136
|
|
|
137
|
+
// Cursor Agent CLI — probe via `agent about` only (≤5s). Never use
|
|
138
|
+
// `agent status` (can hang on login UX while logged out).
|
|
139
|
+
try {
|
|
140
|
+
const agentBinary = resolveAgentBinary()
|
|
141
|
+
if (!agentBinary) {
|
|
142
|
+
checks.cursor = 'error'
|
|
143
|
+
} else {
|
|
144
|
+
await new Promise<void>((resolveCheck, reject) => {
|
|
145
|
+
execFile(agentBinary, ['about'], { timeout: 5000 }, (err, stdout, stderr) => {
|
|
146
|
+
if (err) return reject(err)
|
|
147
|
+
const combined = `${stdout}\n${stderr}`.trim()
|
|
148
|
+
const versionLine = combined.split('\n').map(line => line.trim()).find(line =>
|
|
149
|
+
/CLI Version|cursor|agent/i.test(line),
|
|
150
|
+
)
|
|
151
|
+
checks.cursor = versionLine ?? combined.split('\n')[0] ?? 'available'
|
|
152
|
+
resolveCheck()
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
await getCursorModelCatalog()
|
|
156
|
+
cursorAvailable = isCursorProviderReady()
|
|
157
|
+
if (!cursorAvailable) {
|
|
158
|
+
checks.cursor = typeof checks.cursor === 'string' && checks.cursor !== 'error'
|
|
159
|
+
? `${checks.cursor} (models unresolved)`
|
|
160
|
+
: 'error'
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
checks.cursor = 'error'
|
|
165
|
+
}
|
|
166
|
+
|
|
129
167
|
// Check session cache freshness (COS mode only)
|
|
130
168
|
if (COS_SCRIPTS_DIR) {
|
|
131
169
|
try {
|
|
@@ -161,6 +199,7 @@ healthRouter.get('/health', async (_req, res) => {
|
|
|
161
199
|
const features = {
|
|
162
200
|
claude: claudeAvailable,
|
|
163
201
|
codex: codexAvailable,
|
|
202
|
+
cursor: cursorAvailable,
|
|
164
203
|
voice: keyStatus.hasKey || tts_local.ready,
|
|
165
204
|
cos_pipeline: COS_MODE,
|
|
166
205
|
whisper: isWhisperLocalAvailable(),
|
|
@@ -204,6 +243,10 @@ healthRouter.get('/health', async (_req, res) => {
|
|
|
204
243
|
}
|
|
205
244
|
const openai_whisper_budget = getOpenAIWhisperBudgetState()
|
|
206
245
|
const codex_models = getCodexModelCatalogSnapshot()
|
|
246
|
+
// Unauthenticated /api/health publishes Cursor slot capability only; concrete
|
|
247
|
+
// agent binary paths stay on the authenticated /api/models surface.
|
|
248
|
+
const cursorSnapshot = getCursorModelCatalogSnapshot()
|
|
249
|
+
const { agentBinary: _cursorAgentBinary, ...cursor_models } = cursorSnapshot
|
|
207
250
|
res.json({
|
|
208
251
|
...checks,
|
|
209
252
|
server_version: managedServerVersion(),
|
|
@@ -217,6 +260,7 @@ healthRouter.get('/health', async (_req, res) => {
|
|
|
217
260
|
openai_whisper_budget,
|
|
218
261
|
tts_local,
|
|
219
262
|
codex_models,
|
|
263
|
+
cursor_models,
|
|
220
264
|
capabilities: {
|
|
221
265
|
transcription: { ...transcription, hq: transcriptionHq },
|
|
222
266
|
recovery,
|
|
@@ -240,16 +284,25 @@ healthRouter.get('/health', async (_req, res) => {
|
|
|
240
284
|
})
|
|
241
285
|
})
|
|
242
286
|
|
|
243
|
-
// Stable app slots backed by Codex
|
|
244
|
-
//
|
|
287
|
+
// Stable app slots backed by Codex + Cursor live catalogs. Authenticated by
|
|
288
|
+
// global /api middleware; ?refresh=1 forces discovery.
|
|
245
289
|
healthRouter.get('/models', async (req, res) => {
|
|
246
|
-
const
|
|
290
|
+
const forceRefresh = req.query.refresh === '1'
|
|
291
|
+
const catalog = await getCodexModelCatalog(forceRefresh)
|
|
292
|
+
const cursorCatalog = await getCursorModelCatalog(forceRefresh)
|
|
247
293
|
const durableJobs = durableQueryJobStatus()
|
|
248
294
|
const localFirstMeetings = localFirstMeetingsCapability(getServerInstanceId())
|
|
249
295
|
const transcription = getTranscriptionPolicySnapshot()
|
|
250
296
|
const transcriptionHq = getHighQualityTranscriptionCapability()
|
|
297
|
+
const cursorOptions = cursorCatalog.options.filter(option => !!option.id)
|
|
251
298
|
res.json({
|
|
252
299
|
...catalog,
|
|
300
|
+
options: [
|
|
301
|
+
...(catalog.options ?? []),
|
|
302
|
+
...cursorOptions,
|
|
303
|
+
],
|
|
304
|
+
cursor: cursorCatalog,
|
|
305
|
+
cursorReady: isCursorProviderReady(),
|
|
253
306
|
serverInstanceId: getServerInstanceId(),
|
|
254
307
|
capabilities: {
|
|
255
308
|
durableQueryJobs: {
|
|
@@ -263,7 +316,6 @@ healthRouter.get('/models', async (req, res) => {
|
|
|
263
316
|
},
|
|
264
317
|
})
|
|
265
318
|
})
|
|
266
|
-
|
|
267
319
|
// GET /api/cli-session — returns current CLI session ID for cross-device resume
|
|
268
320
|
healthRouter.get('/cli-session', (req, res) => {
|
|
269
321
|
const cosSessionId = req.query.sid as string | undefined
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
//
|
|
2
|
-
// directory. No COS operations paths, classifiers, or user-specific stores.
|
|
1
|
+
// Meeting archive: COS operations tree when configured, else standalone recordings.
|
|
3
2
|
|
|
4
3
|
import { Router } from 'express'
|
|
5
4
|
import { getMeetingStore, MeetingStore, MeetingStoreError } from '../lib/meeting-store.js'
|
|
5
|
+
import {
|
|
6
|
+
cosOperationsMeetingsConfigured,
|
|
7
|
+
getCosOperationsMeetingDetail,
|
|
8
|
+
listCosOperationsMeetings,
|
|
9
|
+
resolveCosOperationsDir,
|
|
10
|
+
} from '../lib/cos-operations-meetings.js'
|
|
6
11
|
|
|
7
12
|
export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): Router {
|
|
8
13
|
const router = Router()
|
|
@@ -14,7 +19,18 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
|
|
|
14
19
|
const limit = Number.isFinite(rawLimit) ? rawLimit : 20
|
|
15
20
|
const domain = typeof req.query.domain === 'string' ? req.query.domain : 'all'
|
|
16
21
|
res.set('Cache-Control', 'private, no-store')
|
|
17
|
-
|
|
22
|
+
|
|
23
|
+
if (cosOperationsMeetingsConfigured()) {
|
|
24
|
+
const meetings = listCosOperationsMeetings({ limit, domain })
|
|
25
|
+
res.json({
|
|
26
|
+
meetings,
|
|
27
|
+
source: 'cos_operations',
|
|
28
|
+
operationsDir: resolveCosOperationsDir(),
|
|
29
|
+
})
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
res.json({ meetings: store.list({ limit, domain }), source: 'standalone_recordings' })
|
|
18
34
|
} catch (error) {
|
|
19
35
|
sendMeetingStoreError(res, error)
|
|
20
36
|
}
|
|
@@ -32,6 +48,17 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
|
|
|
32
48
|
return
|
|
33
49
|
}
|
|
34
50
|
res.set('Cache-Control', 'private, no-store')
|
|
51
|
+
|
|
52
|
+
if (cosOperationsMeetingsConfigured()) {
|
|
53
|
+
const detail = getCosOperationsMeetingDetail(domain, month, filename)
|
|
54
|
+
if (detail) {
|
|
55
|
+
res.json(detail)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
// Fall through to standalone store for G2-local recordings that share
|
|
59
|
+
// the same API shape when ops lookup misses.
|
|
60
|
+
}
|
|
61
|
+
|
|
35
62
|
res.json(store.detail(domain, month, filename))
|
|
36
63
|
} catch (error) {
|
|
37
64
|
sendMeetingStoreError(res, error)
|
|
@@ -43,6 +70,19 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
|
|
|
43
70
|
router.get('/meetings/:domain/:month/:filename', (req, res) => {
|
|
44
71
|
try {
|
|
45
72
|
res.set('Cache-Control', 'private, no-store')
|
|
73
|
+
|
|
74
|
+
if (cosOperationsMeetingsConfigured()) {
|
|
75
|
+
const detail = getCosOperationsMeetingDetail(
|
|
76
|
+
req.params.domain,
|
|
77
|
+
req.params.month,
|
|
78
|
+
req.params.filename,
|
|
79
|
+
)
|
|
80
|
+
if (detail) {
|
|
81
|
+
res.json(detail)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
46
86
|
res.json(store.detail(req.params.domain, req.params.month, req.params.filename))
|
|
47
87
|
} catch (error) {
|
|
48
88
|
sendMeetingStoreError(res, error)
|
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
getCodexModelCatalog,
|
|
12
12
|
resolveCodexPreferenceForModelId,
|
|
13
13
|
} from '../lib/codex-model-catalog.js'
|
|
14
|
+
import { resolveCursorPreferenceForModelId } from '../lib/cursor-model-catalog.js'
|
|
14
15
|
import { tryInstantResponse } from '../lib/response-cache.js'
|
|
16
|
+
|
|
15
17
|
import crypto from 'node:crypto'
|
|
16
18
|
import { timingSafeTokenEqual } from '../lib/token-auth.js'
|
|
17
19
|
import {
|
|
@@ -80,14 +82,31 @@ function validateAuth(req: any, res: any): boolean {
|
|
|
80
82
|
return true
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Pick the model id an OpenAI-compatible request asked for. A `?model=` on the
|
|
87
|
+
* completion URL wins over the request body, so an Even "Add Agent" endpoint
|
|
88
|
+
* can pin a slot even though the client hardcodes a generic body model.
|
|
89
|
+
*/
|
|
90
|
+
export function selectOpenAICompatibleModel(
|
|
91
|
+
bodyModel?: string,
|
|
92
|
+
urlModel?: string,
|
|
93
|
+
): string | undefined {
|
|
94
|
+
const fromUrl = urlModel?.trim()
|
|
95
|
+
if (fromUrl) return fromUrl
|
|
96
|
+
const fromBody = bodyModel?.trim()
|
|
97
|
+
return fromBody || undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
83
100
|
// Resolve model from OpenAI-compatible ids, stable app slots, or concrete ids
|
|
84
|
-
// currently advertised by the live Codex
|
|
85
|
-
function resolveModel(model?: string, _query?: string): ModelPreference {
|
|
101
|
+
// currently advertised by the live Codex / Cursor catalogs.
|
|
102
|
+
export function resolveModel(model?: string, _query?: string): ModelPreference {
|
|
86
103
|
const normalized = normalizeModelPreference(model)
|
|
87
104
|
if (normalized) return normalized
|
|
88
105
|
if (model) {
|
|
89
106
|
const catalogPreference = resolveCodexPreferenceForModelId(model)
|
|
90
107
|
if (catalogPreference) return catalogPreference
|
|
108
|
+
const cursorPreference = resolveCursorPreferenceForModelId(model)
|
|
109
|
+
if (cursorPreference) return cursorPreference
|
|
91
110
|
}
|
|
92
111
|
if (model === 'cos-opus') return 'opus'
|
|
93
112
|
if (model === 'cos-fable') return 'fable'
|
|
@@ -105,8 +124,9 @@ const MODEL_NAMES: Record<ModelPreference, string> = {
|
|
|
105
124
|
haiku: 'cos-haiku',
|
|
106
125
|
'codex-frontier': 'cos-gpt-frontier',
|
|
107
126
|
'codex-balanced': 'cos-gpt-balanced',
|
|
127
|
+
'cursor-grok': 'cursor-grok',
|
|
128
|
+
'cursor-composer': 'cursor-composer',
|
|
108
129
|
}
|
|
109
|
-
|
|
110
130
|
// Extract the user's latest message from the OpenAI messages array
|
|
111
131
|
function extractUserQuery(messages: Array<{ role: string; content: string }>): string {
|
|
112
132
|
// Find the last user message
|
|
@@ -162,7 +182,11 @@ openaiCompatRouter.post('/v1/chat/completions', async (req, res) => {
|
|
|
162
182
|
console.log(`[g2] Request: stream=${!!stream}, query="${query.slice(0, 50)}"`)
|
|
163
183
|
|
|
164
184
|
const requestReceivedAt = Date.now()
|
|
165
|
-
const
|
|
185
|
+
const selectedModel = selectOpenAICompatibleModel(
|
|
186
|
+
model,
|
|
187
|
+
typeof req.query.model === 'string' ? req.query.model : undefined,
|
|
188
|
+
)
|
|
189
|
+
const resolvedModel = resolveModel(selectedModel, query)
|
|
166
190
|
const completionId = `chatcmpl-${crypto.randomUUID().slice(0, 12)}`
|
|
167
191
|
const timestamp = Math.floor(Date.now() / 1000)
|
|
168
192
|
const responseModel = MODEL_NAMES[resolvedModel]
|
|
@@ -3,7 +3,19 @@ export type ClaudeModelPreference = 'opus' | 'fable' | 'sonnet' | 'haiku'
|
|
|
3
3
|
// Stable app-level slots. Concrete GPT model ids resolve at runtime from the
|
|
4
4
|
// local Codex CLI catalog, so a shipped glasses app keeps tracking new releases.
|
|
5
5
|
export type CodexModelPreference = 'codex-frontier' | 'codex-balanced'
|
|
6
|
-
|
|
6
|
+
// Cursor Agent CLI slots. Concrete ids (composer-2.5 / cursor-grok-4.5-high)
|
|
7
|
+
// resolve from `agent models` at runtime. Slots stay recognized in normalize
|
|
8
|
+
// even when features.cursor is false so version skew fail-closes instead of
|
|
9
|
+
// silently remapping to Claude.
|
|
10
|
+
export type CursorModelPreference = 'cursor-grok' | 'cursor-composer'
|
|
11
|
+
/** Cursor Agent CLI execution posture for glasses queries. */
|
|
12
|
+
export type CursorExecutionMode = 'ask' | 'agent'
|
|
13
|
+
export type ModelPreference = ClaudeModelPreference | CodexModelPreference | CursorModelPreference
|
|
14
|
+
|
|
15
|
+
/** Invalid/omitted → ask (safe for old clients that don't send a mode). */
|
|
16
|
+
export function normalizeCursorExecutionMode(value: unknown): CursorExecutionMode {
|
|
17
|
+
return value === 'agent' ? 'agent' : 'ask'
|
|
18
|
+
}
|
|
7
19
|
|
|
8
20
|
// Preserve the public server's established fast, broadly available default.
|
|
9
21
|
export const DEFAULT_MODEL = 'sonnet' as const
|
|
@@ -11,6 +23,8 @@ export const CODEX_FRONTIER_MODEL: CodexModelPreference = 'codex-frontier'
|
|
|
11
23
|
export const CODEX_BALANCED_MODEL: CodexModelPreference = 'codex-balanced'
|
|
12
24
|
// Backward-compatible export for callers that predate the two-slot catalog.
|
|
13
25
|
export const CODEX_HIGH_MODEL: CodexModelPreference = CODEX_FRONTIER_MODEL
|
|
26
|
+
export const CURSOR_GROK_MODEL: CursorModelPreference = 'cursor-grok'
|
|
27
|
+
export const CURSOR_COMPOSER_MODEL: CursorModelPreference = 'cursor-composer'
|
|
14
28
|
// Existing 6.1–6.3 installs may pin the legacy codex-high slot. Frontier is its
|
|
15
29
|
// migration target; Balanced remains auto-catalog even when this override is set.
|
|
16
30
|
export const CODEX_MODEL_ID = process.env.COS_CODEX_MODEL?.trim() ?? ''
|
|
@@ -33,6 +47,8 @@ export const MODEL_OPTIONS: ModelPreference[] = [
|
|
|
33
47
|
'sonnet',
|
|
34
48
|
CODEX_FRONTIER_MODEL,
|
|
35
49
|
CODEX_BALANCED_MODEL,
|
|
50
|
+
CURSOR_GROK_MODEL,
|
|
51
|
+
CURSOR_COMPOSER_MODEL,
|
|
36
52
|
]
|
|
37
53
|
|
|
38
54
|
const MODEL_SET = new Set<ModelPreference>([
|
|
@@ -42,6 +58,8 @@ const MODEL_SET = new Set<ModelPreference>([
|
|
|
42
58
|
'haiku',
|
|
43
59
|
CODEX_FRONTIER_MODEL,
|
|
44
60
|
CODEX_BALANCED_MODEL,
|
|
61
|
+
CURSOR_GROK_MODEL,
|
|
62
|
+
CURSOR_COMPOSER_MODEL,
|
|
45
63
|
])
|
|
46
64
|
|
|
47
65
|
// Bare Claude tier aliases resolve to the newest model in that tier at spawn.
|
|
@@ -127,12 +145,22 @@ export function isCodexModel(model: ModelPreference): model is CodexModelPrefere
|
|
|
127
145
|
return model === CODEX_FRONTIER_MODEL || model === CODEX_BALANCED_MODEL
|
|
128
146
|
}
|
|
129
147
|
|
|
148
|
+
export function isCursorModel(model: ModelPreference): model is CursorModelPreference {
|
|
149
|
+
return model === CURSOR_GROK_MODEL || model === CURSOR_COMPOSER_MODEL
|
|
150
|
+
}
|
|
151
|
+
|
|
130
152
|
export interface RuntimeCodexModelLabel {
|
|
131
153
|
preference: CodexModelPreference
|
|
132
154
|
displayName: string
|
|
133
155
|
}
|
|
134
156
|
|
|
157
|
+
export interface RuntimeCursorModelLabel {
|
|
158
|
+
preference: CursorModelPreference
|
|
159
|
+
displayName: string
|
|
160
|
+
}
|
|
161
|
+
|
|
135
162
|
const runtimeCodexLabels: Partial<Record<CodexModelPreference, string>> = {}
|
|
163
|
+
const runtimeCursorLabels: Partial<Record<CursorModelPreference, string>> = {}
|
|
136
164
|
|
|
137
165
|
export function setRuntimeCodexModelLabels(options: RuntimeCodexModelLabel[]): void {
|
|
138
166
|
for (const option of options) {
|
|
@@ -147,6 +175,19 @@ export function resetRuntimeCodexModelLabels(): void {
|
|
|
147
175
|
delete runtimeCodexLabels[CODEX_BALANCED_MODEL]
|
|
148
176
|
}
|
|
149
177
|
|
|
178
|
+
export function setRuntimeCursorModelLabels(options: RuntimeCursorModelLabel[]): void {
|
|
179
|
+
for (const option of options) {
|
|
180
|
+
if (!isCursorModel(option.preference)) continue
|
|
181
|
+
const label = typeof option.displayName === 'string' ? option.displayName.trim() : ''
|
|
182
|
+
if (label) runtimeCursorLabels[option.preference] = label
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function resetRuntimeCursorModelLabels(): void {
|
|
187
|
+
delete runtimeCursorLabels[CURSOR_GROK_MODEL]
|
|
188
|
+
delete runtimeCursorLabels[CURSOR_COMPOSER_MODEL]
|
|
189
|
+
}
|
|
190
|
+
|
|
150
191
|
export function modelLabel(model: ModelPreference): string {
|
|
151
192
|
switch (model) {
|
|
152
193
|
case 'fable': return 'Fable'
|
|
@@ -154,6 +195,8 @@ export function modelLabel(model: ModelPreference): string {
|
|
|
154
195
|
case 'haiku': return 'Haiku'
|
|
155
196
|
case 'codex-frontier': return runtimeCodexLabels[model] ?? 'GPT Frontier'
|
|
156
197
|
case 'codex-balanced': return runtimeCodexLabels[model] ?? 'GPT Balanced'
|
|
198
|
+
case 'cursor-grok': return runtimeCursorLabels[model] ?? 'Grok 4.5 Fast'
|
|
199
|
+
case 'cursor-composer': return runtimeCursorLabels[model] ?? 'Composer 2.5 Fast'
|
|
157
200
|
case 'opus':
|
|
158
201
|
default:
|
|
159
202
|
return 'Opus'
|
|
@@ -167,6 +210,8 @@ export function modelShortLabel(model: ModelPreference): string {
|
|
|
167
210
|
case 'haiku': return 'Haiku'
|
|
168
211
|
case 'codex-frontier': return 'GPT Max'
|
|
169
212
|
case 'codex-balanced': return 'GPT Bal'
|
|
213
|
+
case 'cursor-grok': return 'Grok'
|
|
214
|
+
case 'cursor-composer': return 'Composer'
|
|
170
215
|
case 'opus':
|
|
171
216
|
default:
|
|
172
217
|
return 'Opus'
|
|
@@ -180,6 +225,8 @@ export function modelButtonLabel(model: ModelPreference): string {
|
|
|
180
225
|
case 'haiku': return 'HAIKU'
|
|
181
226
|
case 'codex-frontier': return 'GPT MAX'
|
|
182
227
|
case 'codex-balanced': return 'GPT BAL'
|
|
228
|
+
case 'cursor-grok': return 'GROK'
|
|
229
|
+
case 'cursor-composer': return 'CMP'
|
|
183
230
|
case 'opus':
|
|
184
231
|
default:
|
|
185
232
|
return 'OPUS'
|
|
@@ -193,12 +240,13 @@ export function modelTag(model: ModelPreference): string {
|
|
|
193
240
|
case 'haiku': return 'H'
|
|
194
241
|
case 'codex-frontier': return 'GF'
|
|
195
242
|
case 'codex-balanced': return 'GB'
|
|
243
|
+
case 'cursor-grok': return 'GK'
|
|
244
|
+
case 'cursor-composer': return 'C2'
|
|
196
245
|
case 'opus':
|
|
197
246
|
default:
|
|
198
247
|
return 'O'
|
|
199
248
|
}
|
|
200
249
|
}
|
|
201
|
-
|
|
202
250
|
export function modelBracketTag(model: ModelPreference): string {
|
|
203
251
|
return ` [${modelTag(model)}]`
|
|
204
252
|
}
|