Skip to content

Commit 335a58d

Browse files
cuipengfeiclaude
andcommitted
feat: 在日志中显示 premium requests 剩余量
- 在请求完成时显示 [N left] 格式的 premium 配额剩余 - 实时调用 usage API 获取最新数据 - 支持所有 handler 路径 (streaming/non-streaming, ChatCompletions/Responses) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 53b74a9 commit 335a58d

2 files changed

Lines changed: 39 additions & 6 deletions

File tree

.beads/issues.jsonl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
{"id":"copilot-api-0jp","title":"改进 /v1/messages 日志:IN/OUT 格式 + 实时 chunk 计数","description":"目标日志格式:\n[/v1/messages] IN gpt-5-mini → gpt-5 (Responses)\n[/v1/messages] OUT gpt-5 (Responses) 42 chunks ✓\n\n要求:\n- IN:请求进来时打印,原始模型 → 实际模型 (API路径)\n- OUT:streaming 时用 \\r 实时更新 chunk 数,结束后加 ✓ 并换行\n- Non-streaming:打印 OUT model (API) ✓\n\n关键约束:\n- 不能消费 stream(只计数,不读取内容)\n- 不能破坏现有 streaming 行为\n- chunk 计数在循环中递增,不影响数据传输\n\n修改文件:src/routes/messages/handler.ts","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-28T23:33:01.113239475+08:00","updated_at":"2025-11-28T23:39:50.896628851+08:00","closed_at":"2025-11-28T23:39:50.896628851+08:00"}
22
{"id":"copilot-api-b5q","title":"改进模型请求日志:覆盖两条路径并记录原始/实际模型","description":"当前日志只在 ChatCompletions 路径记录 openAIPayload.model。需要:\n1. 在两条路径(ChatCompletions 和 Responses API)都记录日志\n2. 记录原始请求的模型(anthropicPayload.model)\n3. 记录实际使用的模型(翻译后的模型名)\n\n相关文件:\n- src/routes/messages/handler.ts:76-78\n- src/routes/messages/non-stream-translation.ts:49 (translateModelName)\n- src/routes/messages/responses-translation.ts:64 (model 直接透传)","notes":"实施计划:\n\n1. 在 handleCompletion 函数中(路由分叉前)添加统一日志:\n - 记录原始请求模型:anthropicPayload.model\n - 记录使用的 API 路径:ChatCompletions 或 Responses\n\n2. ChatCompletions 路径:\n - 在 handleWithChatCompletions 中记录 openAIPayload.model(翻译后的模型名)\n\n3. Responses API 路径:\n - 在 handleWithResponsesApi 中添加类似日志\n - 记录 responsesPayload.model(Responses API 不翻译模型名)\n\n4. 移除原有的单点日志:handler.ts:76\n\n5. 删除代码中的三个 TODO 注释","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-28T22:52:37.566511883+08:00","updated_at":"2025-11-28T22:56:59.002176965+08:00","closed_at":"2025-11-28T22:56:59.002176965+08:00"}
3+
{"id":"copilot-api-ojy","title":"显示 Premium Requests 剩余数量在日志中","description":"在请求完成日志 ↪ model chunks ✓ 后添加 [remaining/total] 显示 premium requests 剩余量","design":"## 实现方案\n\n1. 修改 OutLogOptions 接口添加 premium 字段\n2. 修改 formatOutLog 函数在 done 时显示 [remaining/total]\n3. 在所有 done:true 调用点获取 getCopilotUsage() 数据\n\n## 修改文件\n- src/routes/messages/handler.ts","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-29T22:05:17.4622557+08:00","updated_at":"2025-11-29T22:07:47.3559476+08:00","closed_at":"2025-11-29T22:07:47.3559476+08:00"}

src/routes/messages/handler.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
type ResponsesResult,
3030
type ResponseStreamEvent,
3131
} from "~/services/copilot/create-responses"
32+
import { getCopilotUsage } from "~/services/github/get-copilot-usage"
3233

3334
import {
3435
type AnthropicMessagesPayload,
@@ -46,10 +47,37 @@ interface OutLogOptions {
4647
model: string
4748
chunks: number
4849
done: boolean
50+
premium?: { remaining: number; total: number } | null
4951
}
5052

51-
const formatOutLog = ({ model, chunks, done }: OutLogOptions): string =>
52-
`\x1b[2K\r↪ ${model} ${chunks}${done ? " ✓" : ""}`
53+
const formatOutLog = ({
54+
model,
55+
chunks,
56+
done,
57+
premium,
58+
}: OutLogOptions): string => {
59+
const base = `\x1b[2K\r↪ ${model} ${chunks}${done ? " ✓" : ""}`
60+
if (done && premium) {
61+
return `${base} [${premium.remaining} left]`
62+
}
63+
return base
64+
}
65+
66+
const getPremiumInfo = async (): Promise<{
67+
remaining: number
68+
total: number
69+
} | null> => {
70+
try {
71+
const usage = await getCopilotUsage()
72+
const pi = usage.quota_snapshots.premium_interactions
73+
if (!pi.unlimited) {
74+
return { remaining: pi.remaining, total: pi.entitlement }
75+
}
76+
} catch {
77+
// Ignore errors, don't affect main flow
78+
}
79+
return null
80+
}
5381

5482
export async function handleCompletion(c: Context) {
5583
await checkRateLimit(state)
@@ -102,8 +130,9 @@ const handleWithChatCompletions = async (
102130
"Translated Anthropic response:",
103131
JSON.stringify(anthropicResponse),
104132
)
133+
const premium = await getPremiumInfo()
105134
process.stdout.write(
106-
`${formatOutLog({ model: openAIPayload.model, chunks: 0, done: true })}\n`,
135+
`${formatOutLog({ model: openAIPayload.model, chunks: 0, done: true, premium })}\n`,
107136
)
108137
return c.json(anthropicResponse)
109138
}
@@ -154,8 +183,9 @@ const handleWithChatCompletions = async (
154183
}
155184
} finally {
156185
clearInterval(pingInterval)
186+
const premium = await getPremiumInfo()
157187
process.stdout.write(
158-
`${formatOutLog({ model: openAIPayload.model, chunks: chunkCount, done: true })}\n`,
188+
`${formatOutLog({ model: openAIPayload.model, chunks: chunkCount, done: true, premium })}\n`,
159189
)
160190
}
161191
})
@@ -245,8 +275,9 @@ const handleWithResponsesApi = async (
245275
}
246276
} finally {
247277
clearInterval(pingInterval)
278+
const premium = await getPremiumInfo()
248279
process.stdout.write(
249-
`${formatOutLog({ model: responsesPayload.model, chunks: chunkCount, done: true })}\n`,
280+
`${formatOutLog({ model: responsesPayload.model, chunks: chunkCount, done: true, premium })}\n`,
250281
)
251282
}
252283
})
@@ -263,8 +294,9 @@ const handleWithResponsesApi = async (
263294
"Translated Anthropic response:",
264295
JSON.stringify(anthropicResponse),
265296
)
297+
const premium = await getPremiumInfo()
266298
process.stdout.write(
267-
`${formatOutLog({ model: responsesPayload.model, chunks: 0, done: true })}\n`,
299+
`${formatOutLog({ model: responsesPayload.model, chunks: 0, done: true, premium })}\n`,
268300
)
269301
return c.json(anthropicResponse)
270302
}

0 commit comments

Comments
 (0)