Skip to content

Commit 3e87bd5

Browse files
cuipengfeiclaude
andcommitted
feat: 改进 /v1/messages 日志显示
- IN 日志显示原始模型 → 实际模型和 API 路径 - OUT 日志实时显示 chunk 计数,完成后显示 ✓ - 使用 ANSI 转义序列实现单行进度更新 - 跳过 /v1/messages 的 Hono logger 避免日志冲突 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3b75171 commit 3e87bd5

3 files changed

Lines changed: 56 additions & 8 deletions

File tree

.beads/issues.jsonl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
{"id":"copilot-api-b5q","content_hash":"544922c60eaba023693cf621f3e2bec394c03c6269b2d79ce4f552fb51232bc7","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","source_repo":"."}
1+
{"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"}
2+
{"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"}

src/routes/messages/handler.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ import { translateChunkToAnthropicEvents } from "./stream-translation"
4242

4343
const logger = createHandlerLogger("messages-handler")
4444

45+
interface OutLogOptions {
46+
model: string
47+
chunks: number
48+
done: boolean
49+
}
50+
51+
const formatOutLog = ({ model, chunks, done }: OutLogOptions): string =>
52+
`\x1b[2K\r↪ ${model} ${chunks}${done ? " ✓" : ""}`
53+
4554
export async function handleCompletion(c: Context) {
4655
await checkRateLimit(state)
4756

@@ -55,10 +64,6 @@ export async function handleCompletion(c: Context) {
5564

5665
const useResponsesApi = shouldUseResponsesApi(anthropicPayload.model)
5766

58-
consola.info(
59-
`[/v1/messages] Original model: ${anthropicPayload.model}, API path: ${useResponsesApi ? "Responses" : "ChatCompletions"}`,
60-
)
61-
6267
if (state.manualApprove) {
6368
await awaitApproval()
6469
}
@@ -77,7 +82,9 @@ const handleWithChatCompletions = async (
7782
anthropicPayload: AnthropicMessagesPayload,
7883
) => {
7984
const openAIPayload = translateToOpenAI(anthropicPayload)
80-
consola.info(`[/v1/messages] Translated model: ${openAIPayload.model}`)
85+
consola.info(
86+
`[/v1/messages] IN ${anthropicPayload.model}${openAIPayload.model} (ChatCompletions)`,
87+
)
8188
logger.debug(
8289
"Translated OpenAI request payload:",
8390
JSON.stringify(openAIPayload),
@@ -95,6 +102,9 @@ const handleWithChatCompletions = async (
95102
"Translated Anthropic response:",
96103
JSON.stringify(anthropicResponse),
97104
)
105+
process.stdout.write(
106+
`${formatOutLog({ model: openAIPayload.model, chunks: 0, done: true })}\n`,
107+
)
98108
return c.json(anthropicResponse)
99109
}
100110

@@ -110,6 +120,7 @@ const handleWithChatCompletions = async (
110120
thinkingBlockOpen: false,
111121
}
112122

123+
let chunkCount = 0
113124
try {
114125
for await (const rawEvent of response) {
115126
logger.debug("Copilot raw stream event:", JSON.stringify(rawEvent))
@@ -121,6 +132,15 @@ const handleWithChatCompletions = async (
121132
continue
122133
}
123134

135+
chunkCount++
136+
process.stdout.write(
137+
formatOutLog({
138+
model: openAIPayload.model,
139+
chunks: chunkCount,
140+
done: false,
141+
}),
142+
)
143+
124144
const chunk = JSON.parse(rawEvent.data) as ChatCompletionChunk
125145
const events = translateChunkToAnthropicEvents(chunk, streamState)
126146

@@ -134,6 +154,9 @@ const handleWithChatCompletions = async (
134154
}
135155
} finally {
136156
clearInterval(pingInterval)
157+
process.stdout.write(
158+
`${formatOutLog({ model: openAIPayload.model, chunks: chunkCount, done: true })}\n`,
159+
)
137160
}
138161
})
139162
}
@@ -144,7 +167,9 @@ const handleWithResponsesApi = async (
144167
) => {
145168
const responsesPayload =
146169
translateAnthropicMessagesToResponsesPayload(anthropicPayload)
147-
consola.info(`[/v1/messages] Using model: ${responsesPayload.model}`)
170+
consola.info(
171+
`[/v1/messages] IN ${anthropicPayload.model}${responsesPayload.model} (Responses)`,
172+
)
148173
logger.debug(
149174
"Translated Responses payload:",
150175
JSON.stringify(responsesPayload),
@@ -163,6 +188,7 @@ const handleWithResponsesApi = async (
163188

164189
const streamState = createResponsesStreamState()
165190

191+
let chunkCount = 0
166192
try {
167193
for await (const chunk of response) {
168194
const eventName = chunk.event
@@ -176,6 +202,15 @@ const handleWithResponsesApi = async (
176202
continue
177203
}
178204

205+
chunkCount++
206+
process.stdout.write(
207+
formatOutLog({
208+
model: responsesPayload.model,
209+
chunks: chunkCount,
210+
done: false,
211+
}),
212+
)
213+
179214
logger.debug("Responses raw stream event:", data)
180215

181216
const events = translateResponsesStreamEvent(
@@ -211,6 +246,9 @@ const handleWithResponsesApi = async (
211246
}
212247
} finally {
213248
clearInterval(pingInterval)
249+
process.stdout.write(
250+
`${formatOutLog({ model: responsesPayload.model, chunks: chunkCount, done: true })}\n`,
251+
)
214252
}
215253
})
216254
}
@@ -226,6 +264,9 @@ const handleWithResponsesApi = async (
226264
"Translated Anthropic response:",
227265
JSON.stringify(anthropicResponse),
228266
)
267+
process.stdout.write(
268+
`${formatOutLog({ model: responsesPayload.model, chunks: 0, done: true })}\n`,
269+
)
229270
return c.json(anthropicResponse)
230271
}
231272

src/server.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import { usageRoute } from "./routes/usage/route"
1313

1414
export const server = new Hono()
1515

16-
server.use(logger())
16+
server.use("*", async (c, next) => {
17+
// /v1/messages 有自己的 IN/OUT 日志,跳过 Hono logger
18+
if (c.req.path.startsWith("/v1/messages")) {
19+
return next()
20+
}
21+
return logger()(c, next)
22+
})
1723
server.use(cors())
1824

1925
server.get("/", (c) => c.text("Server running"))

0 commit comments

Comments
 (0)