@gotcos/glasses-server 6.1.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/.cos-profile.example.json +7 -0
- package/.env.example +44 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/bin/cli.cjs +203 -0
- package/package.json +53 -0
- package/server/env.ts +26 -0
- package/server/index.ts +211 -0
- package/server/lib/archive-budget.ts +65 -0
- package/server/lib/archive.ts +414 -0
- package/server/lib/atomic-fs.ts +50 -0
- package/server/lib/audio-enhance.ts +87 -0
- package/server/lib/claude-bridge.ts +682 -0
- package/server/lib/claude-circuit.ts +52 -0
- package/server/lib/claude-run-ledger.ts +279 -0
- package/server/lib/codex-bridge.ts +476 -0
- package/server/lib/codex-engine-sessions.ts +140 -0
- package/server/lib/codex-run-ledger.ts +298 -0
- package/server/lib/context-builder.ts +210 -0
- package/server/lib/conversation.ts +587 -0
- package/server/lib/data-dir.ts +20 -0
- package/server/lib/display-bus.ts +21 -0
- package/server/lib/display-format.ts +23 -0
- package/server/lib/fuzzy-correct.ts +286 -0
- package/server/lib/hallucination-filter.ts +469 -0
- package/server/lib/local-day.ts +13 -0
- package/server/lib/model-router.ts +38 -0
- package/server/lib/openai-key.ts +155 -0
- package/server/lib/openai-whisper-budget.ts +170 -0
- package/server/lib/profile.ts +94 -0
- package/server/lib/python-bridge.ts +84 -0
- package/server/lib/response-cache.ts +138 -0
- package/server/lib/session-cache-writer.ts +266 -0
- package/server/lib/session-log.ts +162 -0
- package/server/lib/speaker-embeddings.ts +578 -0
- package/server/lib/telegram-notify.ts +85 -0
- package/server/lib/token-audit.ts +50 -0
- package/server/lib/transcribe-audio.ts +187 -0
- package/server/lib/utils.ts +5 -0
- package/server/lib/vad-silero.ts +179 -0
- package/server/lib/whisper-local.ts +697 -0
- package/server/routes/diag.ts +115 -0
- package/server/routes/display.ts +65 -0
- package/server/routes/health.ts +128 -0
- package/server/routes/openai-compat.ts +446 -0
- package/server/routes/openai-key.ts +121 -0
- package/server/routes/query.ts +121 -0
- package/server/routes/transcribe-stream.ts +1090 -0
- package/server/routes/transcribe.ts +55 -0
- package/shared/model-preference.ts +81 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// POST /api/transcribe — Whisper transcription endpoint (message-query / one-shot path).
|
|
2
|
+
//
|
|
3
|
+
// The actual Whisper + cleanup pipeline lives in server/lib/transcribe-audio.ts so
|
|
4
|
+
// prompt-draft recovery and the legacy one-shot path share exactly the same behavior.
|
|
5
|
+
|
|
6
|
+
import { Router } from 'express'
|
|
7
|
+
import {
|
|
8
|
+
transcribeAudioBuffer,
|
|
9
|
+
resolveTranscribeMode,
|
|
10
|
+
NoSpeechDetectedError,
|
|
11
|
+
OpenAIWhisperBudgetExhaustedError,
|
|
12
|
+
} from '../lib/transcribe-audio.js'
|
|
13
|
+
|
|
14
|
+
export const transcribeRouter = Router()
|
|
15
|
+
|
|
16
|
+
function resolveMode(req: { body?: { mode?: string }; query?: { mode?: string | string[] } }) {
|
|
17
|
+
return resolveTranscribeMode(
|
|
18
|
+
(req.body && typeof req.body.mode === 'string' ? req.body.mode : undefined) ??
|
|
19
|
+
(req.query && typeof req.query.mode === 'string' ? req.query.mode : undefined)
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Accept raw binary body up to 25MB (Whisper limit).
|
|
24
|
+
transcribeRouter.post('/transcribe', async (req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
const chunks: Buffer[] = []
|
|
27
|
+
for await (const chunk of req) {
|
|
28
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
|
|
29
|
+
}
|
|
30
|
+
const audioBuffer = Buffer.concat(chunks)
|
|
31
|
+
|
|
32
|
+
if (audioBuffer.length < 100) {
|
|
33
|
+
return res.status(400).json({ error: 'audio too short' })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const result = await transcribeAudioBuffer(audioBuffer, { mode: resolveMode(req) })
|
|
37
|
+
console.log(`[perf] /transcribe: ${result.elapsedMs.toFixed(1)}ms | mode=${result.mode} | ${result.backend} | ${result.audioBytes}b | ${result.text.length} chars`)
|
|
38
|
+
res.json({ text: result.text, backend: result.backend, mode: result.mode })
|
|
39
|
+
} catch (err: any) {
|
|
40
|
+
if (err instanceof NoSpeechDetectedError) {
|
|
41
|
+
console.log(`[perf] /transcribe: DROPPED (hallucination or empty): ${err.rawText.length} chars`)
|
|
42
|
+
return res.status(204).send()
|
|
43
|
+
}
|
|
44
|
+
if (err instanceof OpenAIWhisperBudgetExhaustedError) {
|
|
45
|
+
console.error(`[transcribe] ${err.message}`)
|
|
46
|
+
return res.status(503).json({
|
|
47
|
+
error: err.message,
|
|
48
|
+
reason: 'openai_whisper_budget_exhausted',
|
|
49
|
+
spent_today_usd: err.spentTodayUsd,
|
|
50
|
+
cap_usd: err.capUsd,
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
res.status(500).json({ error: err.message })
|
|
54
|
+
}
|
|
55
|
+
})
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type ClaudeModelPreference = 'opus' | 'sonnet' | 'haiku'
|
|
2
|
+
export type CodexModelPreference = 'codex-high'
|
|
3
|
+
export type ModelPreference = ClaudeModelPreference | CodexModelPreference
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_MODEL = 'opus' as const
|
|
6
|
+
export const CODEX_HIGH_MODEL: CodexModelPreference = 'codex-high'
|
|
7
|
+
|
|
8
|
+
// Optional codex model passed to `codex exec --model`. Empty (the default) means
|
|
9
|
+
// "use whatever model your codex CLI is configured for" — so the public server
|
|
10
|
+
// never pins a specific (possibly unreleased) model id. Set COS_CODEX_MODEL to
|
|
11
|
+
// pin one. COS_CODEX_REASONING_EFFORT tunes the reasoning level (default high).
|
|
12
|
+
export const CODEX_MODEL_ID = process.env.COS_CODEX_MODEL ?? ''
|
|
13
|
+
export const CODEX_HIGH_REASONING_EFFORT = process.env.COS_CODEX_REASONING_EFFORT ?? 'high'
|
|
14
|
+
|
|
15
|
+
export const MODEL_OPTIONS: ModelPreference[] = ['opus', 'sonnet', 'haiku', 'codex-high']
|
|
16
|
+
|
|
17
|
+
const MODEL_SET = new Set<ModelPreference>(['opus', 'sonnet', 'haiku', 'codex-high'])
|
|
18
|
+
|
|
19
|
+
export function isModelPreference(value: unknown): value is ModelPreference {
|
|
20
|
+
return typeof value === 'string' && MODEL_SET.has(value as ModelPreference)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function normalizeModelPreference(value: unknown): ModelPreference | undefined {
|
|
24
|
+
return isModelPreference(value) ? value : undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isClaudeModel(model: ModelPreference): model is ClaudeModelPreference {
|
|
28
|
+
return model === 'opus' || model === 'sonnet' || model === 'haiku'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isCodexModel(model: ModelPreference): model is CodexModelPreference {
|
|
32
|
+
return model === 'codex-high'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function modelLabel(model: ModelPreference): string {
|
|
36
|
+
switch (model) {
|
|
37
|
+
case 'sonnet': return 'Sonnet'
|
|
38
|
+
case 'haiku': return 'Haiku'
|
|
39
|
+
case 'codex-high': return 'Codex High'
|
|
40
|
+
case 'opus':
|
|
41
|
+
default:
|
|
42
|
+
return 'Opus'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function modelShortLabel(model: ModelPreference): string {
|
|
47
|
+
switch (model) {
|
|
48
|
+
case 'sonnet': return 'Sonnet'
|
|
49
|
+
case 'haiku': return 'Haiku'
|
|
50
|
+
case 'codex-high': return 'Codex H'
|
|
51
|
+
case 'opus':
|
|
52
|
+
default:
|
|
53
|
+
return 'Opus'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function modelButtonLabel(model: ModelPreference): string {
|
|
58
|
+
switch (model) {
|
|
59
|
+
case 'sonnet': return 'SNNT'
|
|
60
|
+
case 'haiku': return 'HAIKU'
|
|
61
|
+
case 'codex-high': return 'CODEX H'
|
|
62
|
+
case 'opus':
|
|
63
|
+
default:
|
|
64
|
+
return 'OPUS'
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function modelTag(model: ModelPreference): string {
|
|
69
|
+
switch (model) {
|
|
70
|
+
case 'sonnet': return 'S'
|
|
71
|
+
case 'haiku': return 'H'
|
|
72
|
+
case 'codex-high': return 'CH'
|
|
73
|
+
case 'opus':
|
|
74
|
+
default:
|
|
75
|
+
return 'O'
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function modelBracketTag(model: ModelPreference): string {
|
|
80
|
+
return ` [${modelTag(model)}]`
|
|
81
|
+
}
|