Merge branch 'pr-454'

# Conflicts:
#	cmd/server/main.go
#	internal/translator/claude/openai/chat-completions/claude_openai_response.go
This commit is contained in:
Luis Pater
2026-03-22 22:52:46 +08:00
87 changed files with 3259 additions and 3117 deletions

View File

@@ -209,58 +209,58 @@ func convertCompletionsRequestToChatCompletions(rawJSON []byte) []byte {
}
// Create chat completions structure
out := `{"model":"","messages":[{"role":"user","content":""}]}`
out := []byte(`{"model":"","messages":[{"role":"user","content":""}]}`)
// Set model
if model := root.Get("model"); model.Exists() {
out, _ = sjson.Set(out, "model", model.String())
out, _ = sjson.SetBytes(out, "model", model.String())
}
// Set the prompt as user message content
out, _ = sjson.Set(out, "messages.0.content", prompt)
out, _ = sjson.SetBytes(out, "messages.0.content", prompt)
// Copy other parameters from completions to chat completions
if maxTokens := root.Get("max_tokens"); maxTokens.Exists() {
out, _ = sjson.Set(out, "max_tokens", maxTokens.Int())
out, _ = sjson.SetBytes(out, "max_tokens", maxTokens.Int())
}
if temperature := root.Get("temperature"); temperature.Exists() {
out, _ = sjson.Set(out, "temperature", temperature.Float())
out, _ = sjson.SetBytes(out, "temperature", temperature.Float())
}
if topP := root.Get("top_p"); topP.Exists() {
out, _ = sjson.Set(out, "top_p", topP.Float())
out, _ = sjson.SetBytes(out, "top_p", topP.Float())
}
if frequencyPenalty := root.Get("frequency_penalty"); frequencyPenalty.Exists() {
out, _ = sjson.Set(out, "frequency_penalty", frequencyPenalty.Float())
out, _ = sjson.SetBytes(out, "frequency_penalty", frequencyPenalty.Float())
}
if presencePenalty := root.Get("presence_penalty"); presencePenalty.Exists() {
out, _ = sjson.Set(out, "presence_penalty", presencePenalty.Float())
out, _ = sjson.SetBytes(out, "presence_penalty", presencePenalty.Float())
}
if stop := root.Get("stop"); stop.Exists() {
out, _ = sjson.SetRaw(out, "stop", stop.Raw)
out, _ = sjson.SetRawBytes(out, "stop", []byte(stop.Raw))
}
if stream := root.Get("stream"); stream.Exists() {
out, _ = sjson.Set(out, "stream", stream.Bool())
out, _ = sjson.SetBytes(out, "stream", stream.Bool())
}
if logprobs := root.Get("logprobs"); logprobs.Exists() {
out, _ = sjson.Set(out, "logprobs", logprobs.Bool())
out, _ = sjson.SetBytes(out, "logprobs", logprobs.Bool())
}
if topLogprobs := root.Get("top_logprobs"); topLogprobs.Exists() {
out, _ = sjson.Set(out, "top_logprobs", topLogprobs.Int())
out, _ = sjson.SetBytes(out, "top_logprobs", topLogprobs.Int())
}
if echo := root.Get("echo"); echo.Exists() {
out, _ = sjson.Set(out, "echo", echo.Bool())
out, _ = sjson.SetBytes(out, "echo", echo.Bool())
}
return []byte(out)
return out
}
func convertResponsesObjectToChatCompletion(ctx context.Context, modelName string, originalChatJSON, responsesRequestJSON, responsesPayload []byte) []byte {
@@ -273,10 +273,10 @@ func convertResponsesObjectToChatCompletion(ctx context.Context, modelName strin
}
var param any
converted := codexconverter.ConvertCodexResponseToOpenAINonStream(ctx, modelName, originalChatJSON, responsesRequestJSON, wrapped, &param)
if converted == "" {
if len(converted) == 0 {
return nil
}
return []byte(converted)
return converted
}
func wrapResponsesPayloadAsCompleted(payload []byte) []byte {
@@ -294,7 +294,7 @@ func wrapResponsesPayloadAsCompleted(payload []byte) []byte {
func writeConvertedResponsesChunk(c *gin.Context, ctx context.Context, modelName string, originalChatJSON, responsesRequestJSON, chunk []byte, param *any) {
outputs := codexconverter.ConvertCodexResponseToOpenAI(ctx, modelName, originalChatJSON, responsesRequestJSON, chunk, param)
for _, out := range outputs {
if out == "" {
if len(out) == 0 {
continue
}
_, _ = fmt.Fprintf(c.Writer, "data: %s\n\n", out)
@@ -306,7 +306,7 @@ func (h *OpenAIAPIHandler) forwardResponsesAsChatStream(c *gin.Context, flusher
WriteChunk: func(chunk []byte) {
outputs := codexconverter.ConvertCodexResponseToOpenAI(ctx, modelName, originalChatJSON, responsesRequestJSON, chunk, param)
for _, out := range outputs {
if out == "" {
if len(out) == 0 {
continue
}
_, _ = fmt.Fprintf(c.Writer, "data: %s\n\n", out)
@@ -345,23 +345,23 @@ func convertChatCompletionsResponseToCompletions(rawJSON []byte) []byte {
root := gjson.ParseBytes(rawJSON)
// Base completions response structure
out := `{"id":"","object":"text_completion","created":0,"model":"","choices":[]}`
out := []byte(`{"id":"","object":"text_completion","created":0,"model":"","choices":[]}`)
// Copy basic fields
if id := root.Get("id"); id.Exists() {
out, _ = sjson.Set(out, "id", id.String())
out, _ = sjson.SetBytes(out, "id", id.String())
}
if created := root.Get("created"); created.Exists() {
out, _ = sjson.Set(out, "created", created.Int())
out, _ = sjson.SetBytes(out, "created", created.Int())
}
if model := root.Get("model"); model.Exists() {
out, _ = sjson.Set(out, "model", model.String())
out, _ = sjson.SetBytes(out, "model", model.String())
}
if usage := root.Get("usage"); usage.Exists() {
out, _ = sjson.SetRaw(out, "usage", usage.Raw)
out, _ = sjson.SetRawBytes(out, "usage", []byte(usage.Raw))
}
// Convert choices from chat completions to completions format
@@ -401,10 +401,10 @@ func convertChatCompletionsResponseToCompletions(rawJSON []byte) []byte {
if len(choices) > 0 {
choicesJSON, _ := json.Marshal(choices)
out, _ = sjson.SetRaw(out, "choices", string(choicesJSON))
out, _ = sjson.SetRawBytes(out, "choices", choicesJSON)
}
return []byte(out)
return out
}
// convertChatCompletionsStreamChunkToCompletions converts a streaming chat completions chunk to completions format.
@@ -445,19 +445,19 @@ func convertChatCompletionsStreamChunkToCompletions(chunkData []byte) []byte {
}
// Base completions stream response structure
out := `{"id":"","object":"text_completion","created":0,"model":"","choices":[]}`
out := []byte(`{"id":"","object":"text_completion","created":0,"model":"","choices":[]}`)
// Copy basic fields
if id := root.Get("id"); id.Exists() {
out, _ = sjson.Set(out, "id", id.String())
out, _ = sjson.SetBytes(out, "id", id.String())
}
if created := root.Get("created"); created.Exists() {
out, _ = sjson.Set(out, "created", created.Int())
out, _ = sjson.SetBytes(out, "created", created.Int())
}
if model := root.Get("model"); model.Exists() {
out, _ = sjson.Set(out, "model", model.String())
out, _ = sjson.SetBytes(out, "model", model.String())
}
// Convert choices from chat completions delta to completions format
@@ -496,15 +496,15 @@ func convertChatCompletionsStreamChunkToCompletions(chunkData []byte) []byte {
if len(choices) > 0 {
choicesJSON, _ := json.Marshal(choices)
out, _ = sjson.SetRaw(out, "choices", string(choicesJSON))
out, _ = sjson.SetRawBytes(out, "choices", choicesJSON)
}
// Copy usage if present
if usage := root.Get("usage"); usage.Exists() {
out, _ = sjson.SetRaw(out, "usage", usage.Raw)
out, _ = sjson.SetRawBytes(out, "usage", []byte(usage.Raw))
}
return []byte(out)
return out
}
// handleNonStreamingResponse handles non-streaming chat completion responses

View File

@@ -191,7 +191,7 @@ func (h *OpenAIResponsesAPIHandler) handleNonStreamingResponseViaChat(c *gin.Con
handlers.WriteUpstreamHeaders(c.Writer.Header(), upstreamHeaders)
var param any
converted := responsesconverter.ConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream(cliCtx, modelName, originalResponsesJSON, originalResponsesJSON, resp, &param)
if converted == "" {
if len(converted) == 0 {
h.WriteErrorResponse(c, &interfaces.ErrorMessage{
StatusCode: http.StatusInternalServerError,
Error: fmt.Errorf("failed to convert chat completion response to responses format"),
@@ -199,7 +199,7 @@ func (h *OpenAIResponsesAPIHandler) handleNonStreamingResponseViaChat(c *gin.Con
cliCancel(fmt.Errorf("response conversion failed"))
return
}
_, _ = c.Writer.Write([]byte(converted))
_, _ = c.Writer.Write(converted)
cliCancel()
}
@@ -350,13 +350,13 @@ func (h *OpenAIResponsesAPIHandler) handleStreamingResponseViaChat(c *gin.Contex
func writeChatAsResponsesChunk(c *gin.Context, ctx context.Context, modelName string, originalResponsesJSON, chunk []byte, param *any) {
outputs := responsesconverter.ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx, modelName, originalResponsesJSON, originalResponsesJSON, chunk, param)
for _, out := range outputs {
if out == "" {
if len(out) == 0 {
continue
}
if bytes.HasPrefix([]byte(out), []byte("event:")) {
if bytes.HasPrefix(out, []byte("event:")) {
_, _ = c.Writer.Write([]byte("\n"))
}
_, _ = c.Writer.Write([]byte(out))
_, _ = c.Writer.Write(out)
_, _ = c.Writer.Write([]byte("\n"))
}
}
@@ -366,13 +366,13 @@ func (h *OpenAIResponsesAPIHandler) forwardChatAsResponsesStream(c *gin.Context,
WriteChunk: func(chunk []byte) {
outputs := responsesconverter.ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx, modelName, originalResponsesJSON, originalResponsesJSON, chunk, param)
for _, out := range outputs {
if out == "" {
if len(out) == 0 {
continue
}
if bytes.HasPrefix([]byte(out), []byte("event:")) {
if bytes.HasPrefix(out, []byte("event:")) {
_, _ = c.Writer.Write([]byte("\n"))
}
_, _ = c.Writer.Write([]byte(out))
_, _ = c.Writer.Write(out)
_, _ = c.Writer.Write([]byte("\n"))
}
},

View File

@@ -98,6 +98,9 @@ func (AntigravityAuthenticator) Login(ctx context.Context, cfg *config.Config, o
defer manualPromptTimer.Stop()
}
var manualInputCh <-chan string
var manualInputErrCh <-chan error
waitForCallback:
for {
select {
@@ -115,10 +118,11 @@ waitForCallback:
break waitForCallback
default:
}
input, errPrompt := opts.Prompt("Paste the antigravity callback URL (or press Enter to keep waiting): ")
if errPrompt != nil {
return nil, errPrompt
}
manualInputCh, manualInputErrCh = misc.AsyncPrompt(opts.Prompt, "Paste the antigravity callback URL (or press Enter to keep waiting): ")
continue
case input := <-manualInputCh:
manualInputCh = nil
manualInputErrCh = nil
parsed, errParse := misc.ParseOAuthCallback(input)
if errParse != nil {
return nil, errParse
@@ -132,6 +136,8 @@ waitForCallback:
Error: parsed.Error,
}
break waitForCallback
case errManual := <-manualInputErrCh:
return nil, errManual
case <-timeoutTimer.C:
return nil, fmt.Errorf("antigravity: authentication timed out")
}

View File

@@ -124,6 +124,9 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt
defer manualPromptTimer.Stop()
}
var manualInputCh <-chan string
var manualInputErrCh <-chan error
waitForCallback:
for {
select {
@@ -149,10 +152,11 @@ waitForCallback:
return nil, err
default:
}
input, errPrompt := opts.Prompt("Paste the Claude callback URL (or press Enter to keep waiting): ")
if errPrompt != nil {
return nil, errPrompt
}
manualInputCh, manualInputErrCh = misc.AsyncPrompt(opts.Prompt, "Paste the Claude callback URL (or press Enter to keep waiting): ")
continue
case input := <-manualInputCh:
manualInputCh = nil
manualInputErrCh = nil
parsed, errParse := misc.ParseOAuthCallback(input)
if errParse != nil {
return nil, errParse
@@ -167,6 +171,8 @@ waitForCallback:
Error: parsed.Error,
}
break waitForCallback
case errManual := <-manualInputErrCh:
return nil, errManual
}
}

View File

@@ -127,6 +127,9 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
defer manualPromptTimer.Stop()
}
var manualInputCh <-chan string
var manualInputErrCh <-chan error
waitForCallback:
for {
select {
@@ -152,10 +155,11 @@ waitForCallback:
return nil, err
default:
}
input, errPrompt := opts.Prompt("Paste the Codex callback URL (or press Enter to keep waiting): ")
if errPrompt != nil {
return nil, errPrompt
}
manualInputCh, manualInputErrCh = misc.AsyncPrompt(opts.Prompt, "Paste the Codex callback URL (or press Enter to keep waiting): ")
continue
case input := <-manualInputCh:
manualInputCh = nil
manualInputErrCh = nil
parsed, errParse := misc.ParseOAuthCallback(input)
if errParse != nil {
return nil, errParse
@@ -170,6 +174,8 @@ waitForCallback:
Error: parsed.Error,
}
break waitForCallback
case errManual := <-manualInputErrCh:
return nil, errManual
}
}

View File

@@ -109,6 +109,9 @@ func (a *IFlowAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
defer manualPromptTimer.Stop()
}
var manualInputCh <-chan string
var manualInputErrCh <-chan error
waitForCallback:
for {
select {
@@ -128,10 +131,11 @@ waitForCallback:
return nil, fmt.Errorf("iflow auth: callback wait failed: %w", err)
default:
}
input, errPrompt := opts.Prompt("Paste the iFlow callback URL (or press Enter to keep waiting): ")
if errPrompt != nil {
return nil, errPrompt
}
manualInputCh, manualInputErrCh = misc.AsyncPrompt(opts.Prompt, "Paste the iFlow callback URL (or press Enter to keep waiting): ")
continue
case input := <-manualInputCh:
manualInputCh = nil
manualInputErrCh = nil
parsed, errParse := misc.ParseOAuthCallback(input)
if errParse != nil {
return nil, errParse
@@ -145,6 +149,8 @@ waitForCallback:
Error: parsed.Error,
}
break waitForCallback
case errManual := <-manualInputErrCh:
return nil, errManual
}
}
if result.Error != "" {

View File

@@ -13,16 +13,16 @@ func HasResponseTransformerByFormatName(from, to Format) bool {
}
// TranslateStreamByFormatName converts streaming responses between schemas by their string identifiers.
func TranslateStreamByFormatName(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string {
func TranslateStreamByFormatName(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte {
return TranslateStream(ctx, from, to, model, originalRequestRawJSON, requestRawJSON, rawJSON, param)
}
// TranslateNonStreamByFormatName converts non-streaming responses between schemas by their string identifiers.
func TranslateNonStreamByFormatName(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) string {
func TranslateNonStreamByFormatName(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []byte {
return TranslateNonStream(ctx, from, to, model, originalRequestRawJSON, requestRawJSON, rawJSON, param)
}
// TranslateTokenCountByFormatName converts token counts between schemas by their string identifiers.
func TranslateTokenCountByFormatName(ctx context.Context, from, to Format, count int64, rawJSON []byte) string {
func TranslateTokenCountByFormatName(ctx context.Context, from, to Format, count int64, rawJSON []byte) []byte {
return TranslateTokenCount(ctx, from, to, count, rawJSON)
}

View File

@@ -16,7 +16,7 @@ type ResponseEnvelope struct {
Model string
Stream bool
Body []byte
Chunks []string
Chunks [][]byte
}
// RequestMiddleware decorates request translation.
@@ -87,7 +87,7 @@ func (p *Pipeline) TranslateResponse(ctx context.Context, from, to Format, resp
if input.Stream {
input.Chunks = p.registry.TranslateStream(ctx, from, to, input.Model, originalReq, translatedReq, input.Body, param)
} else {
input.Body = []byte(p.registry.TranslateNonStream(ctx, from, to, input.Model, originalReq, translatedReq, input.Body, param))
input.Body = p.registry.TranslateNonStream(ctx, from, to, input.Model, originalReq, translatedReq, input.Body, param)
}
input.Format = to
return input, nil

View File

@@ -66,7 +66,7 @@ func (r *Registry) HasResponseTransformer(from, to Format) bool {
}
// TranslateStream applies the registered streaming response translator.
func (r *Registry) TranslateStream(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string {
func (r *Registry) TranslateStream(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte {
r.mu.RLock()
defer r.mu.RUnlock()
@@ -75,11 +75,11 @@ func (r *Registry) TranslateStream(ctx context.Context, from, to Format, model s
return fn.Stream(ctx, model, originalRequestRawJSON, requestRawJSON, rawJSON, param)
}
}
return []string{string(rawJSON)}
return [][]byte{rawJSON}
}
// TranslateNonStream applies the registered non-stream response translator.
func (r *Registry) TranslateNonStream(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) string {
func (r *Registry) TranslateNonStream(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []byte {
r.mu.RLock()
defer r.mu.RUnlock()
@@ -88,11 +88,11 @@ func (r *Registry) TranslateNonStream(ctx context.Context, from, to Format, mode
return fn.NonStream(ctx, model, originalRequestRawJSON, requestRawJSON, rawJSON, param)
}
}
return string(rawJSON)
return rawJSON
}
// TranslateNonStream applies the registered non-stream response translator.
func (r *Registry) TranslateTokenCount(ctx context.Context, from, to Format, count int64, rawJSON []byte) string {
// TranslateTokenCount applies the registered token count response translator.
func (r *Registry) TranslateTokenCount(ctx context.Context, from, to Format, count int64, rawJSON []byte) []byte {
r.mu.RLock()
defer r.mu.RUnlock()
@@ -101,7 +101,7 @@ func (r *Registry) TranslateTokenCount(ctx context.Context, from, to Format, cou
return fn.TokenCount(ctx, count)
}
}
return string(rawJSON)
return rawJSON
}
var defaultRegistry = NewRegistry()
@@ -127,16 +127,16 @@ func HasResponseTransformer(from, to Format) bool {
}
// TranslateStream is a helper on the default registry.
func TranslateStream(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string {
func TranslateStream(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte {
return defaultRegistry.TranslateStream(ctx, from, to, model, originalRequestRawJSON, requestRawJSON, rawJSON, param)
}
// TranslateNonStream is a helper on the default registry.
func TranslateNonStream(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) string {
func TranslateNonStream(ctx context.Context, from, to Format, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []byte {
return defaultRegistry.TranslateNonStream(ctx, from, to, model, originalRequestRawJSON, requestRawJSON, rawJSON, param)
}
// TranslateTokenCount is a helper on the default registry.
func TranslateTokenCount(ctx context.Context, from, to Format, count int64, rawJSON []byte) string {
func TranslateTokenCount(ctx context.Context, from, to Format, count int64, rawJSON []byte) []byte {
return defaultRegistry.TranslateTokenCount(ctx, from, to, count, rawJSON)
}

View File

@@ -0,0 +1,52 @@
package translator
import (
"bytes"
"context"
"testing"
)
func TestRegistryTranslateStreamReturnsByteChunks(t *testing.T) {
registry := NewRegistry()
registry.Register(FormatOpenAI, FormatGemini, nil, ResponseTransform{
Stream: func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte {
return [][]byte{append([]byte(nil), rawJSON...)}
},
})
got := registry.TranslateStream(context.Background(), FormatGemini, FormatOpenAI, "model", nil, nil, []byte(`{"chunk":true}`), nil)
if len(got) != 1 {
t.Fatalf("expected 1 chunk, got %d", len(got))
}
if !bytes.Equal(got[0], []byte(`{"chunk":true}`)) {
t.Fatalf("unexpected chunk: %s", got[0])
}
}
func TestRegistryTranslateNonStreamReturnsBytes(t *testing.T) {
registry := NewRegistry()
registry.Register(FormatOpenAI, FormatGemini, nil, ResponseTransform{
NonStream: func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []byte {
return append([]byte(nil), rawJSON...)
},
})
got := registry.TranslateNonStream(context.Background(), FormatGemini, FormatOpenAI, "model", nil, nil, []byte(`{"done":true}`), nil)
if !bytes.Equal(got, []byte(`{"done":true}`)) {
t.Fatalf("unexpected payload: %s", got)
}
}
func TestRegistryTranslateTokenCountReturnsBytes(t *testing.T) {
registry := NewRegistry()
registry.Register(FormatOpenAI, FormatGemini, nil, ResponseTransform{
TokenCount: func(ctx context.Context, count int64) []byte {
return []byte(`{"totalTokens":7}`)
},
})
got := registry.TranslateTokenCount(context.Background(), FormatGemini, FormatOpenAI, 7, []byte(`{"fallback":true}`))
if !bytes.Equal(got, []byte(`{"totalTokens":7}`)) {
t.Fatalf("unexpected payload: %s", got)
}
}

View File

@@ -10,17 +10,17 @@ type RequestTransform func(model string, rawJSON []byte, stream bool) []byte
// ResponseStreamTransform is a function type that converts a streaming response from a source schema to a target schema.
// It takes a context, the model name, the raw JSON of the original and converted requests, the raw JSON of the current response chunk, and an optional parameter.
// It returns a slice of strings, where each string is a chunk of the converted streaming response.
type ResponseStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string
// It returns a slice of byte chunks containing the converted streaming response.
type ResponseStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte
// ResponseNonStreamTransform is a function type that converts a non-streaming response from a source schema to a target schema.
// It takes a context, the model name, the raw JSON of the original and converted requests, the raw JSON of the response, and an optional parameter.
// It returns the converted response as a single string.
type ResponseNonStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) string
// It returns the converted response as a single byte slice.
type ResponseNonStreamTransform func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []byte
// ResponseTokenCountTransform is a function type that transforms a token count from a source format to a target format.
// It takes a context and the token count as an int64, and returns the transformed token count as a string.
type ResponseTokenCountTransform func(ctx context.Context, count int64) string
// It takes a context and the token count as an int64, and returns the transformed token count as bytes.
type ResponseTokenCountTransform func(ctx context.Context, count int64) []byte
// ResponseTransform is a struct that groups together the functions for transforming streaming and non-streaming responses,
// as well as token counts.