mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-09-03 06:35:00 +08:00
fix(antigravity): preserve request plugin hook semantics
This commit is contained in:
@@ -38,13 +38,14 @@ func TranslateRequestWithCodexMultiAgentV2(ctx context.Context, headers http.Hea
|
||||
// TranslateRequestPairWithCodexMultiAgentV2 translates the untouched baseline
|
||||
// payload and the working payload that later stages mutate in place. Executors
|
||||
// normally assign the original payload to the request before translating, so both
|
||||
// translations would rescan the same bytes and produce the same result. Request
|
||||
// translation is deterministic and never aliases its input, so that case is
|
||||
// translated once and duplicated, which removes a full extra pass over payloads
|
||||
// that can reach tens of megabytes.
|
||||
// translations would rescan the same bytes and produce the same result. Built-in
|
||||
// request translation is deterministic, so that case is translated once and
|
||||
// duplicated when no plugin hooks are installed. Hooks retain two invocations
|
||||
// because they may have request-scoped output or side effects. This removes a
|
||||
// full extra pass over payloads that can reach tens of megabytes.
|
||||
func TranslateRequestPairWithCodexMultiAgentV2(ctx context.Context, headers http.Header, cfg *config.Config, from, to sdktranslator.Format, model string, originalPayload, requestPayload []byte, stream bool) (original, working []byte) {
|
||||
original = TranslateRequestWithCodexMultiAgentV2(ctx, headers, cfg, from, to, model, originalPayload, stream)
|
||||
if sameByteSlice(originalPayload, requestPayload) {
|
||||
if sameByteSlice(originalPayload, requestPayload) && !sdktranslator.HasPluginHooks() {
|
||||
// The caller mutates the working copy, so it must not share the baseline array.
|
||||
return original, append([]byte(nil), original...)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,36 @@ import (
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
type pairRequestPluginHooks struct {
|
||||
calls int64
|
||||
}
|
||||
|
||||
func (h *pairRequestPluginHooks) NormalizeRequest(_ context.Context, _, _ sdktranslator.Format, _ string, body []byte, _ bool) []byte {
|
||||
h.calls++
|
||||
updated, _ := sjson.SetBytes(body, "plugin_call", h.calls)
|
||||
return updated
|
||||
}
|
||||
|
||||
func (*pairRequestPluginHooks) TranslateRequest(context.Context, sdktranslator.Format, sdktranslator.Format, string, []byte, bool) ([]byte, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (*pairRequestPluginHooks) NormalizeResponseBefore(context.Context, sdktranslator.Format, sdktranslator.Format, string, []byte, []byte, []byte, bool) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*pairRequestPluginHooks) TranslateResponse(context.Context, sdktranslator.Format, sdktranslator.Format, string, []byte, []byte, []byte, bool) ([]byte, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (*pairRequestPluginHooks) NormalizeResponseAfter(context.Context, sdktranslator.Format, sdktranslator.Format, string, []byte, []byte, []byte, bool) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func geminiToolHistoryPayload(turns int) []byte {
|
||||
contents := []string{`{"role":"user","parts":[{"text":"start"}]}`}
|
||||
for i := 0; i < turns; i++ {
|
||||
@@ -99,6 +127,35 @@ func TestTranslateRequestPairTranslatesDistinctPayloads(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateRequestPairPreservesPluginHookInvocations(t *testing.T) {
|
||||
hooks := &pairRequestPluginHooks{}
|
||||
sdktranslator.SetPluginHooks(hooks)
|
||||
t.Cleanup(func() { sdktranslator.SetPluginHooks(nil) })
|
||||
|
||||
payload := geminiToolHistoryPayload(1)
|
||||
base, work := TranslateRequestPairWithCodexMultiAgentV2(
|
||||
context.Background(),
|
||||
http.Header{},
|
||||
&config.Config{},
|
||||
sdktranslator.FormatGemini,
|
||||
sdktranslator.FromString("antigravity"),
|
||||
"gemini-3.6-flash-high",
|
||||
payload,
|
||||
payload,
|
||||
true,
|
||||
)
|
||||
|
||||
if hooks.calls != 2 {
|
||||
t.Fatalf("plugin hook calls = %d, want 2", hooks.calls)
|
||||
}
|
||||
if got := gjson.GetBytes(base, "plugin_call").Int(); got != 1 {
|
||||
t.Fatalf("baseline plugin_call = %d, want 1", got)
|
||||
}
|
||||
if got := gjson.GetBytes(work, "plugin_call").Int(); got != 2 {
|
||||
t.Fatalf("working plugin_call = %d, want 2", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameByteSlice(t *testing.T) {
|
||||
buf := []byte("payload")
|
||||
cases := []struct {
|
||||
|
||||
@@ -52,6 +52,13 @@ func (r *Registry) SetPluginHooks(hooks PluginHooks) {
|
||||
r.hooks = hooks
|
||||
}
|
||||
|
||||
// HasPluginHooks reports whether request or response translation hooks are installed.
|
||||
func (r *Registry) HasPluginHooks() bool {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.hooks != nil
|
||||
}
|
||||
|
||||
// TranslateRequest converts a payload between schemas, returning the original payload
|
||||
// if no translator is registered. When falling back to the original payload, the
|
||||
// "model" field is still updated to match the resolved model name so that
|
||||
@@ -247,6 +254,11 @@ func SetPluginHooks(hooks PluginHooks) {
|
||||
defaultRegistry.SetPluginHooks(hooks)
|
||||
}
|
||||
|
||||
// HasPluginHooks reports whether hooks are installed on the default registry.
|
||||
func HasPluginHooks() bool {
|
||||
return defaultRegistry.HasPluginHooks()
|
||||
}
|
||||
|
||||
// TranslateRequest is a helper on the default registry.
|
||||
func TranslateRequest(from, to Format, model string, rawJSON []byte, stream bool) []byte {
|
||||
return defaultRegistry.TranslateRequest(from, to, model, rawJSON, stream)
|
||||
|
||||
@@ -61,6 +61,21 @@ func hasCall(calls []string, want string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func TestHasPluginHooks(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
if registry.HasPluginHooks() {
|
||||
t.Fatal("new registry unexpectedly reports plugin hooks")
|
||||
}
|
||||
registry.SetPluginHooks(&fakePluginHooks{})
|
||||
if !registry.HasPluginHooks() {
|
||||
t.Fatal("registry did not report installed plugin hooks")
|
||||
}
|
||||
registry.SetPluginHooks(nil)
|
||||
if registry.HasPluginHooks() {
|
||||
t.Fatal("registry still reports cleared plugin hooks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateRequest_FallbackNormalizesModel(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user