mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-17 04:55:19 +08:00
- Pass target executor identity during Codex token counting and xAI request translation - Skip tool integer normalization when target executor is Codex during compatibility translation - Preserve number types in tool parameter schemas during token counting requests
81 lines
3.1 KiB
Go
81 lines
3.1 KiB
Go
package executor
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
|
|
"github.com/router-for-me/CLIProxyAPI/v8/internal/registry"
|
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/executor"
|
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v8/sdk/translator"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
type codexTokenSchemaHooks struct {
|
|
body []byte
|
|
}
|
|
|
|
func (h *codexTokenSchemaHooks) NormalizeRequest(_ context.Context, _, _ sdktranslator.Format, _ string, body []byte, _ bool) []byte {
|
|
h.body = bytes.Clone(body)
|
|
return body
|
|
}
|
|
|
|
func (*codexTokenSchemaHooks) TranslateRequest(context.Context, sdktranslator.Format, sdktranslator.Format, string, []byte, bool) ([]byte, bool) {
|
|
return nil, false
|
|
}
|
|
|
|
func (*codexTokenSchemaHooks) NormalizeResponseBefore(context.Context, sdktranslator.Format, sdktranslator.Format, string, []byte, []byte, []byte, bool) []byte {
|
|
return nil
|
|
}
|
|
|
|
func (*codexTokenSchemaHooks) TranslateResponse(context.Context, sdktranslator.Format, sdktranslator.Format, string, []byte, []byte, []byte, bool) ([]byte, bool) {
|
|
return nil, false
|
|
}
|
|
|
|
func (*codexTokenSchemaHooks) NormalizeResponseAfter(context.Context, sdktranslator.Format, sdktranslator.Format, string, []byte, []byte, []byte, bool) []byte {
|
|
return nil
|
|
}
|
|
|
|
func TestCodexExecutorCountTokensPreservesToolNumberSchemas(t *testing.T) {
|
|
for _, source := range []struct {
|
|
format sdktranslator.Format
|
|
payload string
|
|
}{
|
|
{format: sdktranslator.FormatOpenAIResponse, payload: `{"input":"hi","tools":[{"type":"function","name":"exec_command","parameters":{"type":"object","properties":{"yield_time_ms":{"type":"number"}}}}]}`},
|
|
{format: sdktranslator.FormatClaude, payload: `{"messages":[{"role":"user","content":"hi"}],"tools":[{"name":"exec_command","input_schema":{"type":"object","properties":{"yield_time_ms":{"type":"number"}}}}]}`},
|
|
} {
|
|
for _, compat := range []bool{false, true} {
|
|
t.Run(fmt.Sprintf("%s/compat=%t", source.format, compat), func(t *testing.T) {
|
|
hooks := &codexTokenSchemaHooks{}
|
|
sdktranslator.SetPluginHooks(hooks)
|
|
t.Cleanup(func() { sdktranslator.SetPluginHooks(nil) })
|
|
exec := NewCodexExecutor(&config.Config{})
|
|
resp, errCount := exec.CountTokens(t.Context(), nil, cliproxyexecutor.Request{
|
|
Model: "gpt-5.4",
|
|
Payload: []byte(source.payload),
|
|
Metadata: map[string]any{
|
|
"cliproxy.resolved_api_key_model_info": ®istry.ModelInfo{ID: "gpt-5.4", IsCompat: compat},
|
|
},
|
|
}, cliproxyexecutor.Options{
|
|
SourceFormat: source.format,
|
|
Headers: http.Header{"User-Agent": []string{"codex_cli_rs/0.1"}},
|
|
})
|
|
if errCount != nil {
|
|
t.Fatalf("CountTokens() error = %v", errCount)
|
|
}
|
|
// Token counts alone can miss a schema rewrite: number and integer
|
|
// may have the same token length. Inspect the translated schema too.
|
|
if got := gjson.GetBytes(hooks.body, "tools.0.parameters.properties.yield_time_ms.type").String(); got != "number" {
|
|
t.Fatalf("yield_time_ms.type = %q, want number; body=%s", got, hooks.body)
|
|
}
|
|
if len(resp.Payload) == 0 {
|
|
t.Fatal("CountTokens() returned an empty response")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|