mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 13:00:52 +08:00
feat(translator): report cached token usage in Claude output
This commit is contained in:
@@ -117,8 +117,12 @@ func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRa
|
|||||||
} else {
|
} else {
|
||||||
template, _ = sjson.Set(template, "delta.stop_reason", "end_turn")
|
template, _ = sjson.Set(template, "delta.stop_reason", "end_turn")
|
||||||
}
|
}
|
||||||
template, _ = sjson.Set(template, "usage.input_tokens", rootResult.Get("response.usage.input_tokens").Int())
|
inputTokens, outputTokens, cachedTokens := extractResponsesUsage(rootResult.Get("response.usage"))
|
||||||
template, _ = sjson.Set(template, "usage.output_tokens", rootResult.Get("response.usage.output_tokens").Int())
|
template, _ = sjson.Set(template, "usage.input_tokens", inputTokens)
|
||||||
|
template, _ = sjson.Set(template, "usage.output_tokens", outputTokens)
|
||||||
|
if cachedTokens > 0 {
|
||||||
|
template, _ = sjson.Set(template, "usage.cache_read_input_tokens", cachedTokens)
|
||||||
|
}
|
||||||
|
|
||||||
output = "event: message_delta\n"
|
output = "event: message_delta\n"
|
||||||
output += fmt.Sprintf("data: %s\n\n", template)
|
output += fmt.Sprintf("data: %s\n\n", template)
|
||||||
@@ -204,8 +208,12 @@ func ConvertCodexResponseToClaudeNonStream(_ context.Context, _ string, original
|
|||||||
out := `{"id":"","type":"message","role":"assistant","model":"","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}`
|
out := `{"id":"","type":"message","role":"assistant","model":"","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}`
|
||||||
out, _ = sjson.Set(out, "id", responseData.Get("id").String())
|
out, _ = sjson.Set(out, "id", responseData.Get("id").String())
|
||||||
out, _ = sjson.Set(out, "model", responseData.Get("model").String())
|
out, _ = sjson.Set(out, "model", responseData.Get("model").String())
|
||||||
out, _ = sjson.Set(out, "usage.input_tokens", responseData.Get("usage.input_tokens").Int())
|
inputTokens, outputTokens, cachedTokens := extractResponsesUsage(responseData.Get("usage"))
|
||||||
out, _ = sjson.Set(out, "usage.output_tokens", responseData.Get("usage.output_tokens").Int())
|
out, _ = sjson.Set(out, "usage.input_tokens", inputTokens)
|
||||||
|
out, _ = sjson.Set(out, "usage.output_tokens", outputTokens)
|
||||||
|
if cachedTokens > 0 {
|
||||||
|
out, _ = sjson.Set(out, "usage.cache_read_input_tokens", cachedTokens)
|
||||||
|
}
|
||||||
|
|
||||||
hasToolCall := false
|
hasToolCall := false
|
||||||
|
|
||||||
@@ -308,12 +316,27 @@ func ConvertCodexResponseToClaudeNonStream(_ context.Context, _ string, original
|
|||||||
out, _ = sjson.SetRaw(out, "stop_sequence", stopSequence.Raw)
|
out, _ = sjson.SetRaw(out, "stop_sequence", stopSequence.Raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
if responseData.Get("usage.input_tokens").Exists() || responseData.Get("usage.output_tokens").Exists() {
|
return out
|
||||||
out, _ = sjson.Set(out, "usage.input_tokens", responseData.Get("usage.input_tokens").Int())
|
}
|
||||||
out, _ = sjson.Set(out, "usage.output_tokens", responseData.Get("usage.output_tokens").Int())
|
|
||||||
|
func extractResponsesUsage(usage gjson.Result) (int64, int64, int64) {
|
||||||
|
if !usage.Exists() || usage.Type == gjson.Null {
|
||||||
|
return 0, 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
inputTokens := usage.Get("input_tokens").Int()
|
||||||
|
outputTokens := usage.Get("output_tokens").Int()
|
||||||
|
cachedTokens := usage.Get("input_tokens_details.cached_tokens").Int()
|
||||||
|
|
||||||
|
if cachedTokens > 0 {
|
||||||
|
if inputTokens >= cachedTokens {
|
||||||
|
inputTokens -= cachedTokens
|
||||||
|
} else {
|
||||||
|
inputTokens = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputTokens, outputTokens, cachedTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildReverseMapFromClaudeOriginalShortToOriginal builds a map[short]original from original Claude request tools.
|
// buildReverseMapFromClaudeOriginalShortToOriginal builds a map[short]original from original Claude request tools.
|
||||||
|
|||||||
@@ -289,21 +289,17 @@ func convertOpenAIStreamingChunkToAnthropic(rawJSON []byte, param *ConvertOpenAI
|
|||||||
// Only process if usage has actual values (not null)
|
// Only process if usage has actual values (not null)
|
||||||
if param.FinishReason != "" {
|
if param.FinishReason != "" {
|
||||||
usage := root.Get("usage")
|
usage := root.Get("usage")
|
||||||
var inputTokens, outputTokens int64
|
var inputTokens, outputTokens, cachedTokens int64
|
||||||
if usage.Exists() && usage.Type != gjson.Null {
|
if usage.Exists() && usage.Type != gjson.Null {
|
||||||
// Check if usage has actual token counts
|
inputTokens, outputTokens, cachedTokens = extractOpenAIUsage(usage)
|
||||||
promptTokens := usage.Get("prompt_tokens")
|
|
||||||
completionTokens := usage.Get("completion_tokens")
|
|
||||||
|
|
||||||
if promptTokens.Exists() && completionTokens.Exists() {
|
|
||||||
inputTokens = promptTokens.Int()
|
|
||||||
outputTokens = completionTokens.Int()
|
|
||||||
}
|
|
||||||
// Send message_delta with usage
|
// Send message_delta with usage
|
||||||
messageDeltaJSON := `{"type":"message_delta","delta":{"stop_reason":"","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}`
|
messageDeltaJSON := `{"type":"message_delta","delta":{"stop_reason":"","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}`
|
||||||
messageDeltaJSON, _ = sjson.Set(messageDeltaJSON, "delta.stop_reason", mapOpenAIFinishReasonToAnthropic(param.FinishReason))
|
messageDeltaJSON, _ = sjson.Set(messageDeltaJSON, "delta.stop_reason", mapOpenAIFinishReasonToAnthropic(param.FinishReason))
|
||||||
messageDeltaJSON, _ = sjson.Set(messageDeltaJSON, "usage.input_tokens", inputTokens)
|
messageDeltaJSON, _ = sjson.Set(messageDeltaJSON, "usage.input_tokens", inputTokens)
|
||||||
messageDeltaJSON, _ = sjson.Set(messageDeltaJSON, "usage.output_tokens", outputTokens)
|
messageDeltaJSON, _ = sjson.Set(messageDeltaJSON, "usage.output_tokens", outputTokens)
|
||||||
|
if cachedTokens > 0 {
|
||||||
|
messageDeltaJSON, _ = sjson.Set(messageDeltaJSON, "usage.cache_read_input_tokens", cachedTokens)
|
||||||
|
}
|
||||||
results = append(results, "event: message_delta\ndata: "+messageDeltaJSON+"\n\n")
|
results = append(results, "event: message_delta\ndata: "+messageDeltaJSON+"\n\n")
|
||||||
param.MessageDeltaSent = true
|
param.MessageDeltaSent = true
|
||||||
|
|
||||||
@@ -423,13 +419,12 @@ func convertOpenAINonStreamingToAnthropic(rawJSON []byte) []string {
|
|||||||
|
|
||||||
// Set usage information
|
// Set usage information
|
||||||
if usage := root.Get("usage"); usage.Exists() {
|
if usage := root.Get("usage"); usage.Exists() {
|
||||||
out, _ = sjson.Set(out, "usage.input_tokens", usage.Get("prompt_tokens").Int())
|
inputTokens, outputTokens, cachedTokens := extractOpenAIUsage(usage)
|
||||||
out, _ = sjson.Set(out, "usage.output_tokens", usage.Get("completion_tokens").Int())
|
out, _ = sjson.Set(out, "usage.input_tokens", inputTokens)
|
||||||
reasoningTokens := int64(0)
|
out, _ = sjson.Set(out, "usage.output_tokens", outputTokens)
|
||||||
if v := usage.Get("completion_tokens_details.reasoning_tokens"); v.Exists() {
|
if cachedTokens > 0 {
|
||||||
reasoningTokens = v.Int()
|
out, _ = sjson.Set(out, "usage.cache_read_input_tokens", cachedTokens)
|
||||||
}
|
}
|
||||||
out, _ = sjson.Set(out, "usage.reasoning_tokens", reasoningTokens)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return []string{out}
|
return []string{out}
|
||||||
@@ -674,8 +669,12 @@ func ConvertOpenAIResponseToClaudeNonStream(_ context.Context, _ string, origina
|
|||||||
}
|
}
|
||||||
|
|
||||||
if respUsage := root.Get("usage"); respUsage.Exists() {
|
if respUsage := root.Get("usage"); respUsage.Exists() {
|
||||||
out, _ = sjson.Set(out, "usage.input_tokens", respUsage.Get("prompt_tokens").Int())
|
inputTokens, outputTokens, cachedTokens := extractOpenAIUsage(respUsage)
|
||||||
out, _ = sjson.Set(out, "usage.output_tokens", respUsage.Get("completion_tokens").Int())
|
out, _ = sjson.Set(out, "usage.input_tokens", inputTokens)
|
||||||
|
out, _ = sjson.Set(out, "usage.output_tokens", outputTokens)
|
||||||
|
if cachedTokens > 0 {
|
||||||
|
out, _ = sjson.Set(out, "usage.cache_read_input_tokens", cachedTokens)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !stopReasonSet {
|
if !stopReasonSet {
|
||||||
@@ -692,3 +691,23 @@ func ConvertOpenAIResponseToClaudeNonStream(_ context.Context, _ string, origina
|
|||||||
func ClaudeTokenCount(ctx context.Context, count int64) string {
|
func ClaudeTokenCount(ctx context.Context, count int64) string {
|
||||||
return fmt.Sprintf(`{"input_tokens":%d}`, count)
|
return fmt.Sprintf(`{"input_tokens":%d}`, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractOpenAIUsage(usage gjson.Result) (int64, int64, int64) {
|
||||||
|
if !usage.Exists() || usage.Type == gjson.Null {
|
||||||
|
return 0, 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
inputTokens := usage.Get("prompt_tokens").Int()
|
||||||
|
outputTokens := usage.Get("completion_tokens").Int()
|
||||||
|
cachedTokens := usage.Get("prompt_tokens_details.cached_tokens").Int()
|
||||||
|
|
||||||
|
if cachedTokens > 0 {
|
||||||
|
if inputTokens >= cachedTokens {
|
||||||
|
inputTokens -= cachedTokens
|
||||||
|
} else {
|
||||||
|
inputTokens = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputTokens, outputTokens, cachedTokens
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user