File size: 4,036 Bytes
1f21206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
 * 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 []
  }
}