codev / src /server /api /status.ts
chenbhao's picture
feat: desktop
1f21206
Raw
History Blame Contribute Delete
4.04 kB
/**
* Status REST API
*
* GET /api/status โ€” ๅฅๅบทๆฃ€ๆŸฅ
* GET /api/status/diagnostics โ€” ็ณป็ปŸ่ฏŠๆ–ญไฟกๆฏ
* GET /api/status/usage โ€” Token ็”จ้‡๏ผˆๅฝ“ๅ‰ไผš่ฏ็ดฏ่ฎก๏ผ‰
* GET /api/status/user โ€” ็”จๆˆทไฟกๆฏ
*/
import * as os from 'os'
import * as path from 'path'
import * as fs from 'fs/promises'
import { ApiError, errorResponse } from '../middleware/errorHandler.js'
// ๆœๅŠกๅ™จๅฏๅŠจๆ—ถ้—ด๏ผˆ็”จไบŽ่ฎก็ฎ— uptime๏ผ‰
const startedAt = Date.now()
// ไผš่ฏ็บงๅˆซ็š„ token ็”จ้‡็ดฏ่ฎก๏ผˆ่ฟ›็จ‹็”Ÿๅ‘ฝๅ‘จๆœŸๅ†…๏ผ‰
const usage = {
totalInputTokens: 0,
totalOutputTokens: 0,
totalCost: 0,
}
/** ไพ›ๅค–้ƒจ็ดฏๅŠ  token ็”จ้‡ */
export function addUsage(input: number, output: number, cost: number) {
usage.totalInputTokens += input
usage.totalOutputTokens += output
usage.totalCost += cost
}
/** ้‡็ฝฎ็”จ้‡๏ผˆๆต‹่ฏ•็”จ๏ผ‰ */
export function resetUsage() {
usage.totalInputTokens = 0
usage.totalOutputTokens = 0
usage.totalCost = 0
}
// โ”€โ”€โ”€ Router โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
export async function handleStatusApi(
req: Request,
_url: URL,
segments: string[],
): Promise<Response> {
try {
if (req.method !== 'GET') {
throw new ApiError(405, `Method ${req.method} not allowed`, 'METHOD_NOT_ALLOWED')
}
const sub = segments[2] // 'diagnostics' | 'usage' | 'user' | undefined
switch (sub) {
case undefined:
return handleHealthCheck()
case 'diagnostics':
return handleDiagnostics()
case 'usage':
return handleUsage()
case 'user':
return await handleUser()
default:
throw ApiError.notFound(`Unknown status endpoint: ${sub}`)
}
} catch (error) {
return errorResponse(error)
}
}
// โ”€โ”€โ”€ Handlers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function handleHealthCheck(): Response {
return Response.json({
status: 'ok',
version: getVersion(),
uptime: Date.now() - startedAt,
})
}
function handleDiagnostics(): Response {
return Response.json({
nodeVersion: process.version,
bunVersion: typeof Bun !== 'undefined' ? Bun.version : 'N/A',
platform: process.platform,
arch: process.arch,
configDir: getConfigDir(),
memory: {
rss: process.memoryUsage.rss(),
heapUsed: process.memoryUsage().heapUsed,
heapTotal: process.memoryUsage().heapTotal,
},
})
}
function handleUsage(): Response {
return Response.json({
totalInputTokens: usage.totalInputTokens,
totalOutputTokens: usage.totalOutputTokens,
totalCost: usage.totalCost,
})
}
async function handleUser(): Promise<Response> {
const configDir = getConfigDir()
const projects = await discoverProjects(configDir)
return Response.json({
configDir,
projects,
})
}
// โ”€โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function getConfigDir(): string {
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude')
}
function getVersion(): string {
// ไปŽ package.json ็š„ version ๅญ—ๆฎต่ฏปๅ–๏ผ›ๅ›ž้€€ๅˆฐ็Žฏๅขƒๅ˜้‡ๆˆ– unknown
return process.env.APP_VERSION || '999.0.0-local'
}
/**
* ๆ‰ซๆ configDir ไธ‹็š„ projects ็›ฎๅฝ•๏ผŒ่ฟ”ๅ›žๅทฒ็Ÿฅ็š„้กน็›ฎ่ทฏๅพ„ๅˆ—่กจใ€‚
* ๅฆ‚ๆžœ็›ฎๅฝ•ไธๅญ˜ๅœจ๏ผŒ่ฟ”ๅ›ž็ฉบๆ•ฐ็ป„ใ€‚
*/
async function discoverProjects(configDir: string): Promise<string[]> {
const projectsDir = path.join(configDir, 'projects')
try {
const entries = await fs.readdir(projectsDir)
return entries.filter((e) => !e.startsWith('.'))
} catch {
return []
}
}