Ctrl K
ComunidadGuías…

internal/pluginhost/adapters_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package pluginhost

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path/filepath"
	"sort"
	"strings"
	"testing"
	"time"

	"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
	"github.com/router-for-me/CLIProxyAPI/v8/internal/registry"
	sdkaccess "github.com/router-for-me/CLIProxyAPI/v8/sdk/access"
	coreauth "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/auth"
	coreexecutor "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/executor"
	coreusage "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/usage"
	"github.com/router-for-me/CLIProxyAPI/v8/sdk/pluginabi"
	"github.com/router-for-me/CLIProxyAPI/v8/sdk/pluginapi"
	sdktranslator "github.com/router-for-me/CLIProxyAPI/v8/sdk/translator"
)

func TestPluginModelInfoToRegistryModelInfoClonesThinkingAndSlices(t *testing.T) {
	model := pluginapi.ModelInfo{
		ID:                         "model-1",
		Object:                     "model",
		Created:                    123,
		OwnedBy:                    "owner",
		Type:                       "plugin",
		DisplayName:                "Model One",
		Name:                       "provider-model",
		Version:                    "v1",
		Description:                "desc",
		InputTokenLimit:            100,
		OutputTokenLimit:           200,
		SupportedGenerationMethods: []string{"generate"},
		ContextLength:              300,
		MaxCompletionTokens:        400,
		SupportedParameters:        []string{"temperature"},
		SupportedInputModalities:   []string{"text"},
		SupportedOutputModalities:  []string{"image"},
		Thinking: &pluginapi.ThinkingSupport{
			Min:            1,
			Max:            2,
			ZeroAllowed:    true,
			DynamicAllowed: true,
			Levels:         []string{"low", "high"},
		},
		UserDefined: true,
	}

	got := pluginModelInfoToRegistryModelInfo(model)
	if got.ID != model.ID || got.Object != model.Object || got.Created != model.Created || got.OwnedBy != model.OwnedBy || got.Type != model.Type ||
		got.DisplayName != model.DisplayName || got.Name != model.Name || got.Version != model.Version || got.Description != model.Description ||
		got.InputTokenLimit != int(model.InputTokenLimit) || got.OutputTokenLimit != int(model.OutputTokenLimit) ||
		got.ContextLength != int(model.ContextLength) || got.MaxCompletionTokens != int(model.MaxCompletionTokens) || !got.UserDefined {
		t.Fatalf("converted model = %#v, want fields copied from %#v", got, model)
	}
	if got.Thinking == nil {
		t.Fatal("Thinking = nil, want converted thinking support")
	}
	if got.Thinking.Min != 1 || got.Thinking.Max != 2 || !got.Thinking.ZeroAllowed || !got.Thinking.DynamicAllowed || fmt.Sprint(got.Thinking.Levels) != "[low high]" {
		t.Fatalf("Thinking = %#v, want copied thinking support", got.Thinking)
	}

	model.SupportedGenerationMethods[0] = "mutated"
	model.SupportedParameters[0] = "mutated"
	model.SupportedInputModalities[0] = "mutated"
	model.SupportedOutputModalities[0] = "mutated"
	model.Thinking.Levels[0] = "mutated"
	if got.SupportedGenerationMethods[0] != "generate" || got.SupportedParameters[0] != "temperature" ||
		got.SupportedInputModalities[0] != "text" || got.SupportedOutputModalities[0] != "image" ||
		got.Thinking.Levels[0] != "low" {
		t.Fatalf("converted model kept aliases to plugin slices: %#v", got)
	}
}

func TestExecutorNativeStreamResponseTranslatorExistsRequiresStreamTransform(t *testing.T) {
	outputFormat := sdktranslator.Format("plugin-output-non-stream-only")
	requestedFormat := sdktranslator.Format("client-output-non-stream-only")
	sdktranslator.Register(requestedFormat, outputFormat, nil, sdktranslator.ResponseTransform{
		NonStream: func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []byte {
			return rawJSON
		},
	})

	if executorNativeStreamResponseTranslatorExists(outputFormat, requestedFormat) {
		t.Fatal("non-stream-only response transformer was accepted for stream executor output")
	}

	streamOutputFormat := sdktranslator.Format("plugin-output-stream")
	streamRequestedFormat := sdktranslator.Format("client-output-stream")
	sdktranslator.Register(streamRequestedFormat, streamOutputFormat, nil, sdktranslator.ResponseTransform{
		Stream: func(ctx context.Context, model string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte {
			return [][]byte{rawJSON}
		},
	})

	if !executorNativeStreamResponseTranslatorExists(streamOutputFormat, streamRequestedFormat) {
		t.Fatal("stream response transformer was not accepted for stream executor output")
	}
}

func TestRegisterModelsRetainsCatalogWithoutPublishingClient(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	host := newHostWithRecords(capabilityRecord{
		id:   "alpha",
		meta: pluginapi.Metadata{Name: "Alpha", Version: "1.0.0"},
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: modelRegistrarFunc(func(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
				if req.Plugin.Name != "Alpha" || req.Plugin.Version != "1.0.0" {
					t.Fatalf("RegisterModels request plugin = %#v, want Alpha metadata", req.Plugin)
				}
				return pluginapi.ModelRegistrationResponse{
					Provider: "  MixedProvider  ",
					Models: []pluginapi.ModelInfo{{
						ID:                         " model-1 ",
						Object:                     "model",
						Created:                    123,
						OwnedBy:                    "owner",
						Type:                       "chat",
						DisplayName:                "Model One",
						Name:                       "native-model-1",
						Version:                    "v1",
						Description:                "description",
						InputTokenLimit:            100,
						OutputTokenLimit:           200,
						SupportedGenerationMethods: []string{"generate"},
						ContextLength:              300,
						MaxCompletionTokens:        400,
						SupportedParameters:        []string{"temperature"},
						SupportedInputModalities:   []string{"text"},
						SupportedOutputModalities:  []string{"text"},
						Thinking: &pluginapi.ThinkingSupport{
							Min:            1,
							Max:            2,
							ZeroAllowed:    true,
							DynamicAllowed: true,
							Levels:         []string{"low"},
						},
						UserDefined: true,
					}},
				}, nil
			}),
		}},
	})

	host.RegisterModels(context.Background(), modelRegistry)

	host.RegisterExecutors(newFakeExecutorManager(), modelRegistry)
	if len(modelRegistry.clients) != 0 {
		t.Fatalf("registered clients = %#v, want none without a routable credential", modelRegistry.clients)
	}
	if models := host.ModelsForProvider("mixedprovider"); len(models) != 1 || models[0].ID != "model-1" {
		t.Fatalf("provider catalog = %#v, want model-1 for per-auth registration", models)
	}
	reg := host.modelRegistration("alpha")
	if reg.provider != "mixedprovider" {
		t.Fatalf("registered provider = %q, want mixedprovider", reg.provider)
	}
	if len(reg.models) != 1 {
		t.Fatalf("registered model count = %d, want 1", len(reg.models))
	}
	model := reg.models[0]
	if model.ID != "model-1" || model.Object != "model" || model.Created != 123 || model.OwnedBy != "owner" || model.Type != "chat" ||
		model.DisplayName != "Model One" || model.Name != "native-model-1" || model.Version != "v1" || model.Description != "description" ||
		model.InputTokenLimit != 100 || model.OutputTokenLimit != 200 || model.ContextLength != 300 || model.MaxCompletionTokens != 400 ||
		model.SupportedGenerationMethods[0] != "generate" || model.SupportedParameters[0] != "temperature" ||
		model.SupportedInputModalities[0] != "text" || model.SupportedOutputModalities[0] != "text" || !model.UserDefined {
		t.Fatalf("registered model = %#v, want converted fields", model)
	}
	if model.Thinking == nil || model.Thinking.Min != 1 || model.Thinking.Max != 2 || !model.Thinking.ZeroAllowed ||
		!model.Thinking.DynamicAllowed || model.Thinking.Levels[0] != "low" {
		t.Fatalf("registered thinking = %#v, want converted thinking", model.Thinking)
	}
}

func TestRegisterModelsUsesModelProviderStaticModels(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	called := false
	host := newHostWithRecords(capabilityRecord{
		id:   "alpha",
		meta: pluginapi.Metadata{Name: "Alpha", Version: "1.0.0"},
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelProvider: modelProviderFunc{
				staticModels: func(ctx context.Context, req pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error) {
					called = true
					if req.Plugin.Name != "Alpha" || req.Plugin.Version != "1.0.0" {
						t.Fatalf("StaticModels request plugin = %#v, want Alpha metadata", req.Plugin)
					}
					if req.Host.AuthDir != "/tmp/plugin-auth" || req.Host.ProxyURL != "http://proxy.local" || !req.Host.ForceModelPrefix {
						t.Fatalf("StaticModels host = %#v, want configured summary", req.Host)
					}
					if len(req.Host.OAuthModelAlias["plugin-provider"]) != 1 || req.Host.OAuthModelAlias["plugin-provider"][0].Alias != "alias-model" {
						t.Fatalf("StaticModels OAuthModelAlias = %#v, want configured alias", req.Host.OAuthModelAlias)
					}
					if len(req.Host.ExcludedModels["plugin-provider"]) != 1 || req.Host.ExcludedModels["plugin-provider"][0] != "hidden-model" {
						t.Fatalf("StaticModels ExcludedModels = %#v, want configured exclusion", req.Host.ExcludedModels)
					}
					return pluginapi.ModelResponse{
						Provider: "  Plugin-Provider  ",
						Models: []pluginapi.ModelInfo{{
							ID:          " model-static ",
							Object:      "model",
							DisplayName: "Static Model",
						}},
					}, nil
				},
			},
			ModelRegistrar: staticModelRegistrar("legacy-provider", "legacy-model"),
		}},
	})
	host.runtimeConfig = &config.Config{
		SDKConfig: config.SDKConfig{
			ProxyURL:         "http://proxy.local",
			ForceModelPrefix: true,
		},
		AuthDir: "/tmp/plugin-auth",
		OAuthModelAlias: map[string][]config.OAuthModelAlias{
			"plugin-provider": []config.OAuthModelAlias{{Name: "upstream-model", Alias: "alias-model"}},
		},
		OAuthExcludedModels: map[string][]string{
			"plugin-provider": []string{"hidden-model"},
		},
	}

	host.RegisterModels(context.Background(), modelRegistry)

	if !called {
		t.Fatal("ModelProvider.StaticModels was not called")
	}
	host.RegisterExecutors(newFakeExecutorManager(), modelRegistry)
	if len(modelRegistry.clients) != 0 {
		t.Fatalf("registered clients = %#v, want none without a routable credential", modelRegistry.clients)
	}
	if models := host.ModelsForProvider("plugin-provider"); len(models) != 1 || models[0].ID != "model-static" {
		t.Fatalf("provider catalog = %#v, want model-static for per-auth registration", models)
	}
	reg := host.modelRegistration("alpha")
	if reg.provider != "plugin-provider" {
		t.Fatalf("registered provider = %q, want plugin-provider", reg.provider)
	}
	if len(reg.models) != 1 || reg.models[0].ID != "model-static" || reg.models[0].DisplayName != "Static Model" {
		t.Fatalf("registered models = %#v, want static model", reg.models)
	}
	if len(host.ModelsForProvider("legacy-provider")) != 0 {
		t.Fatal("legacy ModelRegistrar path was used despite ModelProvider.StaticModels")
	}
}

func TestRegisterModelsSkipsErrorEmptyAndInvalidModels(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	host := newHostWithRecords(
		capabilityRecord{
			id: "error",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ModelRegistrar: modelRegistrarFunc(func(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
					return pluginapi.ModelRegistrationResponse{}, errors.New("register failed")
				}),
			}},
		},
		capabilityRecord{
			id: "empty-provider",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ModelRegistrar: modelRegistrarFunc(func(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
					return pluginapi.ModelRegistrationResponse{Provider: " ", Models: []pluginapi.ModelInfo{{ID: "model"}}}, nil
				}),
			}},
		},
		capabilityRecord{
			id: "empty-models",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ModelRegistrar: modelRegistrarFunc(func(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
					return pluginapi.ModelRegistrationResponse{Provider: "provider"}, nil
				}),
			}},
		},
		capabilityRecord{
			id: "invalid-models",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ModelRegistrar: modelRegistrarFunc(func(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
					return pluginapi.ModelRegistrationResponse{Provider: "provider", Models: []pluginapi.ModelInfo{{ID: " "}}}, nil
				}),
			}},
		},
	)

	host.RegisterModels(context.Background(), modelRegistry)

	if len(modelRegistry.clients) != 0 {
		t.Fatalf("registered clients = %#v, want none", modelRegistry.clients)
	}
	if registrations := host.snapshotModelRegistrations(); len(registrations) != 0 {
		t.Fatalf("model registrations = %#v, want none", registrations)
	}
}

func TestRegisterModelsPrunesStaleCatalogAfterSnapshotChange(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: staticModelRegistrar("provider-a", "model-a"),
		}},
	})
	manager := newFakeExecutorManager()
	host.RegisterModels(context.Background(), modelRegistry)
	host.RegisterExecutors(manager, modelRegistry)
	if models := host.ModelsForProvider("provider-a"); len(models) != 1 || models[0].ID != "model-a" {
		t.Fatalf("initial provider catalog = %#v, want model-a", models)
	}

	setHostSnapshotForTest(host, true, capabilityRecord{
		id: "bravo",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: staticModelRegistrar("provider-b", "model-b"),
		}},
	})
	host.RegisterModels(context.Background(), modelRegistry)

	host.RegisterExecutors(manager, modelRegistry)
	if len(host.ModelsForProvider("provider-a")) != 0 || len(host.modelRegistration("alpha").models) != 0 {
		t.Fatal("stale alpha catalog is still retained")
	}
	if models := host.ModelsForProvider("provider-b"); len(models) != 1 || models[0].ID != "model-b" {
		t.Fatalf("provider catalog after reload = %#v, want model-b", models)
	}
	if len(modelRegistry.clients) != 0 || len(modelRegistry.unregisters) != 0 {
		t.Fatalf("catalog reload modified registry clients: clients=%#v, unregisters=%#v", modelRegistry.clients, modelRegistry.unregisters)
	}
}

func TestRegisterModelsDropsResultsWhenSnapshotChangesDuringRegistration(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	host := New()
	oldRecord := capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: modelRegistrarFunc(func(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
				setHostSnapshotForTest(host, true, capabilityRecord{
					id: "bravo",
					plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
						ModelRegistrar: staticModelRegistrar("provider-b", "model-b"),
					}},
				})
				return pluginapi.ModelRegistrationResponse{
					Provider: "provider-a",
					Models: []pluginapi.ModelInfo{{
						ID: "model-a",
					}},
				}, nil
			}),
		}},
	}
	setHostSnapshotForTest(host, true, oldRecord)
	host.modelProviders["alpha"] = "existing-provider"

	host.RegisterModels(context.Background(), modelRegistry)

	if len(modelRegistry.clients) != 0 {
		t.Fatalf("registered clients = %#v, want none after stale snapshot", modelRegistry.clients)
	}
	if len(modelRegistry.unregisters) != 0 {
		t.Fatalf("unregistered clients = %#v, want none after stale snapshot", modelRegistry.unregisters)
	}
	if host.modelProvider("alpha") != "existing-provider" {
		t.Fatalf("model provider = %q, want existing-provider", host.modelProvider("alpha"))
	}
}

func TestRegisterModelsPanicFusesPluginAndSkipsLaterCalls(t *testing.T) {
	calls := 0
	modelRegistry := newFakeModelRegistry()
	host := newHostWithRecords(capabilityRecord{
		id: "panic-plugin",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: modelRegistrarFunc(func(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
				calls++
				panic("register models panic")
			}),
		}},
	})

	host.RegisterModels(context.Background(), modelRegistry)
	host.RegisterModels(context.Background(), modelRegistry)

	if calls != 1 {
		t.Fatalf("RegisterModels calls = %d, want 1", calls)
	}
	if !host.isPluginFused("panic-plugin") {
		t.Fatal("panic-plugin was not fused")
	}
	if len(modelRegistry.clients) != 0 {
		t.Fatalf("registered clients = %#v, want none", modelRegistry.clients)
	}
}

func TestRegisterExecutorsDoesNotOverwriteExistingExecutor(t *testing.T) {
	manager := newFakeExecutorManager()
	existing := &fakeProviderExecutor{provider: "provider"}
	manager.RegisterExecutor(existing)
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			Executor: &fakeExecutor{identifier: "provider"},
		}},
	})

	host.RegisterExecutors(manager, nil)

	if manager.registerCalls != 1 {
		t.Fatalf("RegisterExecutor calls = %d, want only existing registration", manager.registerCalls)
	}
	got, _ := manager.Executor("provider")
	if got != existing {
		t.Fatalf("registered executor = %#v, want existing executor", got)
	}
}

func TestRegisterExecutorsSameProviderKeepsFirstSnapshotCandidate(t *testing.T) {
	manager := newFakeExecutorManager()
	first := &fakeExecutor{identifier: "provider"}
	second := &fakeExecutor{identifier: "provider"}
	host := newHostWithRecords(
		capabilityRecord{
			id:       "low",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				Executor: second,
			}},
		},
		capabilityRecord{
			id:       "high",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				Executor: first,
			}},
		},
	)

	host.RegisterExecutors(manager, nil)

	if manager.registerCalls != 1 {
		t.Fatalf("RegisterExecutor calls = %d, want 1", manager.registerCalls)
	}
	adapter, okAdapter := manager.executors["provider"].(*executorAdapter)
	if !okAdapter {
		t.Fatalf("registered executor = %#v, want executorAdapter", manager.executors["provider"])
	}
	if adapter.pluginID != "high" || adapter.executor != first {
		t.Fatalf("registered adapter = %#v, want high priority executor", adapter)
	}
}

func TestRegisterExecutorsIdentifierPanicFusesPlugin(t *testing.T) {
	manager := newFakeExecutorManager()
	host := newHostWithRecords(capabilityRecord{
		id: "panic-identifier",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			Executor: &fakeExecutor{panicIdentifier: true},
		}},
	})

	host.RegisterExecutors(manager, nil)

	if !host.isPluginFused("panic-identifier") {
		t.Fatal("panic-identifier was not fused")
	}
	if manager.registerCalls != 0 {
		t.Fatalf("RegisterExecutor calls = %d, want 0", manager.registerCalls)
	}
}

func TestRegisterExecutorsSelectsHighestPriorityPluginExecutorPerModel(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	host := newHostWithRecords(
		capabilityRecord{
			id:       "low",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ModelRegistrar: staticModelRegistrar("low-provider", "shared-model"),
				Executor:       &fakeExecutor{identifier: "low-provider"},
			}},
		},
		capabilityRecord{
			id:       "high",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ModelRegistrar: staticModelRegistrar("high-provider", "shared-model"),
				Executor:       &fakeExecutor{identifier: "high-provider"},
			}},
		},
	)
	host.RegisterModels(context.Background(), modelRegistry)

	host.RegisterExecutors(manager, modelRegistry)

	if _, okLow := manager.executors["low-provider"]; okLow {
		t.Fatal("low priority executor was registered for shared-model")
	}
	if _, okHigh := manager.executors["high-provider"]; !okHigh {
		t.Fatal("high priority executor was not registered for shared-model")
	}
	if got := host.ModelsForProvider("low-provider"); len(got) != 0 {
		t.Fatalf("low provider models = %#v, want none", got)
	}
	got := host.ModelsForProvider("high-provider")
	if len(got) != 1 || got[0].ID != "shared-model" {
		t.Fatalf("high provider models = %#v, want shared-model", got)
	}
}

func TestRegisterExecutorsKeepsPluginModelsForNativeProviderWithoutOverwritingExecutor(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	native := &fakeProviderExecutor{provider: "native-provider"}
	manager.RegisterExecutor(native)
	host := newHostWithRecords(capabilityRecord{
		id:       "native-extension",
		priority: 20,
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: staticModelRegistrar("native-provider", "native-extension-model"),
			Executor:       &fakeExecutor{identifier: "native-provider"},
		}},
	})
	host.RegisterModels(context.Background(), modelRegistry)

	host.RegisterExecutors(manager, modelRegistry)

	if manager.registerCalls != 1 {
		t.Fatalf("RegisterExecutor calls = %d, want only native registration", manager.registerCalls)
	}
	gotExecutor, _ := manager.Executor("native-provider")
	if gotExecutor != native {
		t.Fatalf("native provider executor = %#v, want native executor", gotExecutor)
	}
	gotModels := host.ModelsForProvider("native-provider")
	if len(gotModels) != 1 || gotModels[0].ID != "native-extension-model" {
		t.Fatalf("native provider plugin models = %#v, want native-extension-model", gotModels)
	}
}

func TestRegisterExecutorsSkipsPluginModelWhenModelAlreadyHasNativeExecutor(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	modelRegistry.RegisterClient("native-auth", "native-provider", []*registry.ModelInfo{{ID: "shared-model"}})
	manager := newFakeExecutorManager()
	manager.RegisterExecutor(&fakeProviderExecutor{provider: "native-provider"})
	host := newHostWithRecords(capabilityRecord{
		id:       "plugin-executor",
		priority: 20,
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: staticModelRegistrar("plugin-provider", "shared-model"),
			Executor:       &fakeExecutor{identifier: "plugin-provider"},
		}},
	})
	host.RegisterModels(context.Background(), modelRegistry)

	host.RegisterExecutors(manager, modelRegistry)

	if _, okPlugin := manager.executors["plugin-provider"]; okPlugin {
		t.Fatal("plugin executor was registered for a model that already has a native executor")
	}
	if got := host.ModelsForProvider("plugin-provider"); len(got) != 0 {
		t.Fatalf("plugin provider models = %#v, want none", got)
	}
}

func TestRegisterExecutorsUsesRegisteredModelProviderBeforeFallback(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	exec := &fakeExecutor{identifier: "fallback-provider"}
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: staticModelRegistrar("registered-provider", "model"),
			Executor:       exec,
		}},
	})
	host.RegisterModels(context.Background(), modelRegistry)

	host.RegisterExecutors(manager, modelRegistry)

	adapter, okAdapter := manager.executors["registered-provider"].(*executorAdapter)
	if !okAdapter {
		t.Fatalf("registered executor = %#v, want executorAdapter", manager.executors["registered-provider"])
	}
	if adapter.provider != "registered-provider" || adapter.executor != exec {
		t.Fatalf("adapter = %#v, want registered provider executor", adapter)
	}
	if _, okFallback := manager.executors["fallback-provider"]; okFallback {
		t.Fatal("fallback provider was registered despite model provider cache")
	}
}

func TestRegisterExecutorsExposesExecutorModelsForUserAuthBinding(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	exec := &fakeExecutor{identifier: "plugin-provider"}
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: staticModelRegistrar("plugin-provider", "plugin-model"),
			Executor:       exec,
		}},
	})
	host.RegisterModels(context.Background(), modelRegistry)

	if len(modelRegistry.clients) != 0 {
		t.Fatalf("registered model clients = %#v, want none until a matching auth binds provider models", modelRegistry.clients)
	}

	host.RegisterExecutors(manager, modelRegistry)

	if _, okExecutor := manager.executors["plugin-provider"]; !okExecutor {
		t.Fatal("plugin provider executor was not registered")
	}
	models := host.ModelsForProvider("plugin-provider")
	if len(models) != 1 || models[0].ID != "plugin-model" {
		t.Fatalf("provider models = %#v, want plugin-model for user auth binding", models)
	}
	clientID := pluginExecutorModelClientID("alpha", "plugin-provider")
	reg := modelRegistry.clients[clientID]
	if reg == nil {
		t.Fatalf("executor model client %s was not registered", clientID)
	}
	if reg.provider != "plugin-provider" || len(reg.models) != 1 || reg.models[0].ID != "plugin-model" {
		t.Fatalf("executor model registry client = %#v, want plugin-provider/plugin-model", reg)
	}
	if providers := modelRegistry.GetModelProviders("plugin-model"); len(providers) != 1 || providers[0] != "plugin-provider" {
		t.Fatalf("providers for plugin-model = %#v, want plugin-provider", providers)
	}
}

func TestRegisterExecutorsOAuthScopeSkipsStaticModelClientButRegistersExecutor(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	staticCalled := false
	host := newHostWithRecords(capabilityRecord{
		id: "sample-provider",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			AuthProvider: fakeAuthProvider{identifier: "sample-provider"},
			ModelProvider: modelProviderFunc{
				staticModels: func(ctx context.Context, req pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error) {
					staticCalled = true
					return pluginapi.ModelResponse{
						Provider: "sample-provider",
						Models:   []pluginapi.ModelInfo{{ID: "static-model"}},
					}, nil
				},
				modelsForAuth: func(ctx context.Context, req pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error) {
					return pluginapi.ModelResponse{
						Provider: "sample-provider",
						Models:   []pluginapi.ModelInfo{{ID: "oauth-model"}},
					}, nil
				},
			},
			Executor:           &fakeExecutor{identifier: "sample-provider"},
			ExecutorModelScope: pluginapi.ExecutorModelScopeOAuth,
		}},
	})

	host.RegisterModels(context.Background(), modelRegistry)
	host.RegisterExecutors(manager, modelRegistry)

	if staticCalled {
		t.Fatal("StaticModels was called for an OAuth-only executor")
	}
	if _, okExecutor := manager.executors["sample-provider"]; !okExecutor {
		t.Fatal("OAuth-only executor was not registered")
	}
	if _, okClient := modelRegistry.clients[pluginExecutorModelClientID("sample-provider", "sample-provider")]; okClient {
		t.Fatal("OAuth-only executor registered a static model client")
	}
	if got := host.ModelsForProvider("sample-provider"); len(got) != 0 {
		t.Fatalf("OAuth-only provider models = %#v, want none", got)
	}

	result := host.ModelsForAuth(context.Background(), &coreauth.Auth{
		ID:       "sample-provider-auth",
		Provider: "sample-provider",
	})
	if !result.Handled || result.Provider != "sample-provider" || len(result.Models) != 1 || result.Models[0].ID != "oauth-model" {
		t.Fatalf("OAuth model result = %#v, want oauth-model", result)
	}
}

func TestModelsForAuthOAuthScopeFallsBackToExecutorIdentifier(t *testing.T) {
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelProvider: modelProviderFunc{
				modelsForAuth: func(ctx context.Context, req pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error) {
					return pluginapi.ModelResponse{
						Provider: "plugin-provider",
						Models:   []pluginapi.ModelInfo{{ID: "oauth-model"}},
					}, nil
				},
			},
			Executor:           &fakeExecutor{identifier: "plugin-provider"},
			ExecutorModelScope: pluginapi.ExecutorModelScopeOAuth,
		}},
	})

	result := host.ModelsForAuth(context.Background(), &coreauth.Auth{
		ID:       "plugin-auth",
		Provider: "plugin-provider",
	})

	if !result.Handled || result.Provider != "plugin-provider" || len(result.Models) != 1 || result.Models[0].ID != "oauth-model" {
		t.Fatalf("OAuth model result = %#v, want executor-identifier match", result)
	}
}

func TestRegisterExecutorsStaticScopeSkipsModelsForAuth(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	modelsForAuthCalled := false
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			AuthProvider: fakeAuthProvider{identifier: "plugin-provider"},
			ModelProvider: modelProviderFunc{
				staticModels: func(ctx context.Context, req pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error) {
					return pluginapi.ModelResponse{
						Provider: "plugin-provider",
						Models:   []pluginapi.ModelInfo{{ID: "static-model"}},
					}, nil
				},
				modelsForAuth: func(ctx context.Context, req pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error) {
					modelsForAuthCalled = true
					return pluginapi.ModelResponse{
						Provider: "plugin-provider",
						Models:   []pluginapi.ModelInfo{{ID: "oauth-model"}},
					}, nil
				},
			},
			Executor:           &fakeExecutor{identifier: "plugin-provider"},
			ExecutorModelScope: pluginapi.ExecutorModelScopeStatic,
		}},
	})

	host.RegisterModels(context.Background(), modelRegistry)
	host.RegisterExecutors(manager, modelRegistry)

	clientID := pluginExecutorModelClientID("alpha", "plugin-provider")
	reg := modelRegistry.clients[clientID]
	if reg == nil || reg.provider != "plugin-provider" || len(reg.models) != 1 || reg.models[0].ID != "static-model" {
		t.Fatalf("static executor model client = %#v, want static-model", reg)
	}
	result := host.ModelsForAuth(context.Background(), &coreauth.Auth{
		ID:       "plugin-auth",
		Provider: "plugin-provider",
	})
	if result.Handled {
		t.Fatalf("static-only executor handled per-auth models: %#v", result)
	}
	if modelsForAuthCalled {
		t.Fatal("ModelsForAuth was called for a static-only executor")
	}
}

func TestRegisterExecutorsBothScopeKeepsStaticAndOAuthModels(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			AuthProvider: fakeAuthProvider{identifier: "plugin-provider"},
			ModelProvider: modelProviderFunc{
				staticModels: func(ctx context.Context, req pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error) {
					return pluginapi.ModelResponse{
						Provider: "plugin-provider",
						Models:   []pluginapi.ModelInfo{{ID: "static-model"}},
					}, nil
				},
				modelsForAuth: func(ctx context.Context, req pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error) {
					return pluginapi.ModelResponse{
						Provider: "plugin-provider",
						Models:   []pluginapi.ModelInfo{{ID: "oauth-model"}},
					}, nil
				},
			},
			Executor:           &fakeExecutor{identifier: "plugin-provider"},
			ExecutorModelScope: pluginapi.ExecutorModelScopeBoth,
		}},
	})

	host.RegisterModels(context.Background(), modelRegistry)
	host.RegisterExecutors(manager, modelRegistry)

	clientID := pluginExecutorModelClientID("alpha", "plugin-provider")
	reg := modelRegistry.clients[clientID]
	if reg == nil || reg.provider != "plugin-provider" || len(reg.models) != 1 || reg.models[0].ID != "static-model" {
		t.Fatalf("both-scope static model client = %#v, want static-model", reg)
	}
	result := host.ModelsForAuth(context.Background(), &coreauth.Auth{
		ID:       "plugin-auth",
		Provider: "plugin-provider",
	})
	if !result.Handled || result.Provider != "plugin-provider" || len(result.Models) != 1 || result.Models[0].ID != "oauth-model" {
		t.Fatalf("both-scope OAuth model result = %#v, want oauth-model", result)
	}
}

func TestRegisterExecutorsDropsResultsWhenSnapshotChangesBeforeCommit(t *testing.T) {
	manager := newFakeExecutorManager()
	host := New()
	staleExecutor := &executorAdapter{
		host:     host,
		pluginID: "stale",
		provider: "stale-provider",
	}
	manager.executors["stale-provider"] = staleExecutor
	host.executorProviders["stale-provider"] = struct{}{}

	changedSnapshot := false
	exec := &fakeExecutor{
		identifierFunc: func() string {
			if !changedSnapshot {
				changedSnapshot = true
				setHostSnapshotForTest(host, true)
			}
			return "provider-a"
		},
	}
	setHostSnapshotForTest(host, true, capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			Executor: exec,
		}},
	})

	host.RegisterExecutors(manager, nil)

	if manager.registerCalls != 0 {
		t.Fatalf("RegisterExecutor calls = %d, want none for stale snapshot", manager.registerCalls)
	}
	if _, okProvider := manager.executors["provider-a"]; okProvider {
		t.Fatal("provider-a executor was registered from a stale snapshot")
	}
	if manager.executors["stale-provider"] != staleExecutor {
		t.Fatalf("stale-provider executor = %#v, want existing executor preserved", manager.executors["stale-provider"])
	}
	if _, okProvider := host.executorProviders["stale-provider"]; !okProvider {
		t.Fatal("stale-provider ownership was pruned by a stale snapshot")
	}
}

func TestRegisterExecutorsFallbackUsesExecutorIdentifier(t *testing.T) {
	manager := newFakeExecutorManager()
	exec := &fakeExecutor{identifier: "  FallbackProvider  "}
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			Executor: exec,
		}},
	})

	host.RegisterExecutors(manager, nil)

	adapter, okAdapter := manager.executors["fallbackprovider"].(*executorAdapter)
	if !okAdapter {
		t.Fatalf("registered executor = %#v, want fallback executorAdapter", manager.executors["fallbackprovider"])
	}
	if adapter.provider != "fallbackprovider" || adapter.executor != exec {
		t.Fatalf("adapter = %#v, want fallback provider executor", adapter)
	}
}

func TestRegisterExecutorsPrunesStaleProviderAfterMigration(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	exec := &fakeExecutor{identifier: "fallback-provider"}
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ModelRegistrar: staticModelRegistrar("provider-a", "plugin-model"),
			Executor:       exec,
		}},
	})
	host.modelProviders["alpha"] = "provider-a"
	host.modelRegistrations["alpha"] = pluginModelRegistration{
		pluginID:    "alpha",
		provider:    "provider-a",
		models:      []*registry.ModelInfo{{ID: "plugin-model"}},
		hasExecutor: true,
	}
	host.RegisterExecutors(manager, modelRegistry)

	host.modelProviders["alpha"] = "provider-b"
	host.modelRegistrations["alpha"] = pluginModelRegistration{
		pluginID:    "alpha",
		provider:    "provider-b",
		models:      []*registry.ModelInfo{{ID: "plugin-model"}},
		hasExecutor: true,
	}
	host.RegisterExecutors(manager, modelRegistry)

	if _, okProvider := manager.executors["provider-a"]; okProvider {
		t.Fatal("provider-a executor is still registered")
	}
	if manager.unregisters[0] != "provider-a" {
		t.Fatalf("unregistered providers = %#v, want provider-a", manager.unregisters)
	}
	adapter, okAdapter := manager.executors["provider-b"].(*executorAdapter)
	if !okAdapter {
		t.Fatalf("provider-b executor = %#v, want executorAdapter", manager.executors["provider-b"])
	}
	if adapter.executor != exec {
		t.Fatalf("provider-b adapter executor = %#v, want migrated executor", adapter.executor)
	}
	if _, okClient := modelRegistry.clients[pluginExecutorModelClientID("alpha", "provider-a")]; okClient {
		t.Fatal("provider-a executor model client is still registered")
	}
	if _, okClient := modelRegistry.clients[pluginExecutorModelClientID("alpha", "provider-b")]; !okClient {
		t.Fatal("provider-b executor model client was not registered")
	}
}

func TestOwnsExecutorDistinguishesHostAdapters(t *testing.T) {
	host := New()
	owned := &executorAdapter{host: host}
	foreign := &executorAdapter{host: New()}
	external := &fakeProviderExecutor{provider: "provider-a"}

	if !host.OwnsExecutor(owned) {
		t.Fatal("host did not recognize its executor adapter")
	}
	if host.OwnsExecutor(foreign) {
		t.Fatal("host claimed another host's executor adapter")
	}
	if host.OwnsExecutor(external) {
		t.Fatal("host claimed an externally owned executor")
	}
}

func TestRegisterExecutorsDoesNotUnregisterStaleProviderOwnedExternally(t *testing.T) {
	manager := newFakeExecutorManager()
	exec := &fakeExecutor{identifier: "fallback-provider"}
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			Executor: exec,
		}},
	})
	host.modelProviders["alpha"] = "provider-a"
	host.RegisterExecutors(manager, nil)

	external := &fakeProviderExecutor{provider: "provider-a"}
	manager.executors["provider-a"] = external
	host.modelProviders["alpha"] = "provider-b"
	host.RegisterExecutors(manager, nil)

	if len(manager.unregisters) != 0 {
		t.Fatalf("unregistered providers = %#v, want none for external owner", manager.unregisters)
	}
	if manager.executors["provider-a"] != external {
		t.Fatalf("provider-a executor = %#v, want external executor", manager.executors["provider-a"])
	}
	if _, okProvider := manager.executors["provider-b"]; !okProvider {
		t.Fatal("provider-b executor was not registered")
	}
}

func TestRegisterExecutorsCommitsManagerChangesOutsideHostLock(t *testing.T) {
	manager := newFakeExecutorManager()
	exec := &fakeExecutor{identifier: "fallback-provider"}
	host := newHostWithRecords(capabilityRecord{
		id: "alpha",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			Executor: exec,
		}},
	})
	host.modelProviders["alpha"] = "provider-a"

	assertUnlocked := func(op string) {
		t.Helper()
		if !host.mu.TryLock() {
			t.Fatalf("%s called while host.mu is held", op)
		}
		host.mu.Unlock()
	}
	manager.onExecutor = func(string) {
		assertUnlocked("manager.Executor")
	}
	manager.onRegister = func(coreauth.ProviderExecutor) {
		assertUnlocked("manager.RegisterExecutor")
	}
	manager.onUnregister = func(string) {
		assertUnlocked("manager.UnregisterExecutor")
	}

	host.RegisterExecutors(manager, nil)
	host.modelProviders["alpha"] = "provider-b"
	host.RegisterExecutors(manager, nil)

	if manager.registerCalls != 2 {
		t.Fatalf("RegisterExecutor calls = %d, want 2", manager.registerCalls)
	}
	if len(manager.unregisters) != 1 || manager.unregisters[0] != "provider-a" {
		t.Fatalf("unregistered providers = %#v, want [provider-a]", manager.unregisters)
	}
}

func TestNormalizeRequestChainsByPriority(t *testing.T) {
	host := newHostWithRecords(
		capabilityRecord{
			id:       "high",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestNormalizer: requestNormalizerFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: append(req.Body, []byte("|high")...)}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "low",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestNormalizer: requestNormalizerFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: append(req.Body, []byte("|low")...)}, nil
				}),
			}},
		},
	)

	got := host.NormalizeRequest(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", []byte("start"), false)
	if string(got) != "start|high|low" {
		t.Fatalf("NormalizeRequest() = %q, want %q", got, "start|high|low")
	}
}

func TestTranslateRequestStopsAtFirstSuccessfulCandidate(t *testing.T) {
	calls := make([]string, 0, 2)
	host := newHostWithRecords(
		capabilityRecord{
			id:       "high",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestTranslator: requestTranslatorFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					calls = append(calls, "high")
					return pluginapi.PayloadResponse{Body: []byte("translated-high")}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "low",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestTranslator: requestTranslatorFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					calls = append(calls, "low")
					return pluginapi.PayloadResponse{Body: []byte("translated-low")}, nil
				}),
			}},
		},
	)

	got, ok := host.TranslateRequest(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", []byte("input"), false)
	if !ok {
		t.Fatal("TranslateRequest() ok = false, want true")
	}
	if string(got) != "translated-high" {
		t.Fatalf("TranslateRequest() = %q, want %q", got, "translated-high")
	}
	if fmt.Sprint(calls) != "[high]" {
		t.Fatalf("calls = %v, want [high]", calls)
	}
}

func TestAdaptersKeepPayloadOrTryNextOnErrorAndEmptyBody(t *testing.T) {
	host := newHostWithRecords(
		capabilityRecord{
			id:       "normalizer-error",
			priority: 30,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestNormalizer: requestNormalizerFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, fmt.Errorf("normalize failed")
				}),
			}},
		},
		capabilityRecord{
			id:       "normalizer-empty",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestNormalizer: requestNormalizerFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "normalizer-success",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestNormalizer: requestNormalizerFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: []byte("kept-then-success")}, nil
				}),
			}},
		},
	)

	normalized := host.NormalizeRequest(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", []byte("original"), false)
	if string(normalized) != "kept-then-success" {
		t.Fatalf("NormalizeRequest() = %q, want %q", normalized, "kept-then-success")
	}

	translatorHost := newHostWithRecords(
		capabilityRecord{
			id:       "translator-error",
			priority: 30,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestTranslator: requestTranslatorFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, fmt.Errorf("translate failed")
				}),
			}},
		},
		capabilityRecord{
			id:       "translator-empty",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestTranslator: requestTranslatorFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "translator-success",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestTranslator: requestTranslatorFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: []byte("translated")}, nil
				}),
			}},
		},
	)

	translated, ok := translatorHost.TranslateRequest(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", []byte("original"), false)
	if !ok {
		t.Fatal("TranslateRequest() ok = false, want true")
	}
	if string(translated) != "translated" {
		t.Fatalf("TranslateRequest() = %q, want %q", translated, "translated")
	}
}

func TestTranslatorPanicFusesPlugin(t *testing.T) {
	host := newHostWithRecords(
		capabilityRecord{
			id:       "panic-plugin",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestNormalizer: requestNormalizerFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					panic("normalize panic")
				}),
			}},
		},
		capabilityRecord{
			id:       "next-plugin",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestNormalizer: requestNormalizerFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: append(req.Body, []byte("|next")...)}, nil
				}),
			}},
		},
	)

	got := host.NormalizeRequest(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", []byte("original"), false)
	if string(got) != "original|next" {
		t.Fatalf("NormalizeRequest() = %q, want %q", got, "original|next")
	}
	if !host.isPluginFused("panic-plugin") {
		t.Fatal("panic-plugin was not fused")
	}
}

func TestTranslatorPanicFusesEveryHookPath(t *testing.T) {
	cases := []struct {
		name     string
		pluginID string
		call     func(*Host) ([]byte, bool)
	}{
		{
			name:     "request translator",
			pluginID: "request-translator-panic",
			call: func(host *Host) ([]byte, bool) {
				setHostSnapshotForTest(host, true, capabilityRecord{
					id:       "request-translator-panic",
					priority: 10,
					plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
						RequestTranslator: requestTranslatorFunc(func(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
							panic("request translator panic")
						}),
					}},
				})
				return host.TranslateRequest(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", []byte("body"), false)
			},
		},
		{
			name:     "response before normalizer",
			pluginID: "response-before-panic",
			call: func(host *Host) ([]byte, bool) {
				setHostSnapshotForTest(host, true, capabilityRecord{
					id:       "response-before-panic",
					priority: 10,
					plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
						ResponseBeforeTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
							panic("response before panic")
						}),
					}},
				})
				return host.NormalizeResponseBefore(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", nil, nil, []byte("body"), false), false
			},
		},
		{
			name:     "response translator",
			pluginID: "response-translator-panic",
			call: func(host *Host) ([]byte, bool) {
				setHostSnapshotForTest(host, true, capabilityRecord{
					id:       "response-translator-panic",
					priority: 10,
					plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
						ResponseTranslator: responseTranslatorFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
							panic("response translator panic")
						}),
					}},
				})
				return host.TranslateResponse(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", nil, nil, []byte("body"), false)
			},
		},
		{
			name:     "response after normalizer",
			pluginID: "response-after-panic",
			call: func(host *Host) ([]byte, bool) {
				setHostSnapshotForTest(host, true, capabilityRecord{
					id:       "response-after-panic",
					priority: 10,
					plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
						ResponseAfterTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
							panic("response after panic")
						}),
					}},
				})
				return host.NormalizeResponseAfter(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", nil, nil, []byte("body"), false), false
			},
		},
	}

	for _, tt := range cases {
		t.Run(tt.name, func(t *testing.T) {
			host := New()
			got, _ := tt.call(host)
			if string(got) != "body" {
				t.Fatalf("hook result = %q, want original body", got)
			}
			if !host.isPluginFused(tt.pluginID) {
				t.Fatalf("%s was not fused", tt.pluginID)
			}
		})
	}
}

func TestResponseNormalizersChainByPriority(t *testing.T) {
	host := newHostWithRecords(
		capabilityRecord{
			id:       "high",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseBeforeTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: append(req.Body, []byte("|before-high")...)}, nil
				}),
				ResponseAfterTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: append(req.Body, []byte("|after-high")...)}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "low",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseBeforeTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: append(req.Body, []byte("|before-low")...)}, nil
				}),
				ResponseAfterTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: append(req.Body, []byte("|after-low")...)}, nil
				}),
			}},
		},
	)

	before := host.NormalizeResponseBefore(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", []byte("original-request"), []byte("translated-request"), []byte("body"), true)
	if string(before) != "body|before-high|before-low" {
		t.Fatalf("NormalizeResponseBefore() = %q, want %q", before, "body|before-high|before-low")
	}
	after := host.NormalizeResponseAfter(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", []byte("original-request"), []byte("translated-request"), []byte("body"), true)
	if string(after) != "body|after-high|after-low" {
		t.Fatalf("NormalizeResponseAfter() = %q, want %q", after, "body|after-high|after-low")
	}
}

func TestTranslateResponseStopsAtFirstSuccessfulCandidate(t *testing.T) {
	calls := make([]string, 0, 2)
	host := newHostWithRecords(
		capabilityRecord{
			id:       "high",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseTranslator: responseTranslatorFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					calls = append(calls, "high")
					return pluginapi.PayloadResponse{Body: []byte("response-high")}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "low",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseTranslator: responseTranslatorFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					calls = append(calls, "low")
					return pluginapi.PayloadResponse{Body: []byte("response-low")}, nil
				}),
			}},
		},
	)

	got, ok := host.TranslateResponse(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", nil, nil, []byte("input"), false)
	if !ok {
		t.Fatal("TranslateResponse() ok = false, want true")
	}
	if string(got) != "response-high" {
		t.Fatalf("TranslateResponse() = %q, want %q", got, "response-high")
	}
	if fmt.Sprint(calls) != "[high]" {
		t.Fatalf("calls = %v, want [high]", calls)
	}
}

func TestInterceptRequestChainsByPriorityAndHeaders(t *testing.T) {
	host := newHostWithRecords(
		capabilityRecord{
			id:       "high",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					if req.SourceFormat != "openai" || req.Model != "normalized" || req.RequestedModel != "requested" {
						t.Fatalf("unexpected request context: %#v", req)
					}
					return pluginapi.RequestInterceptResponse{
						Headers: http.Header{"X-Plugin": []string{"high"}},
						Body:    append(req.Body, []byte("|high")...),
					}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "low",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					return pluginapi.RequestInterceptResponse{
						Headers:      http.Header{"X-Plugin": []string{"low"}, "X-Low": []string{"1"}},
						Body:         append(req.Body, []byte("|low")...),
						ClearHeaders: []string{"X-Remove"},
					}, nil
				}),
			}},
		},
	)
	headers := http.Header{"X-Remove": []string{"yes"}}

	got := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{
		SourceFormat:   "openai",
		Model:          "normalized",
		RequestedModel: "requested",
		Stream:         false,
		Headers:        headers,
		Body:           []byte("start"),
	})

	if string(got.Body) != "start|high|low" {
		t.Fatalf("body = %q, want %q", got.Body, "start|high|low")
	}
	if got.Headers.Get("X-Plugin") != "low" || got.Headers.Get("X-Low") != "1" || got.Headers.Get("X-Remove") != "" {
		t.Fatalf("headers = %#v", got.Headers)
	}
	if headers.Get("X-Plugin") != "" {
		t.Fatalf("input headers were mutated: %#v", headers)
	}
}

func TestInterceptRequestAfterAuthPassesTargetFormat(t *testing.T) {
	host := newHostWithRecords(capabilityRecord{
		id: "after",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
				if req.SourceFormat != "openai" || req.ToFormat != "codex" {
					t.Fatalf("request formats = %q -> %q, want openai -> codex", req.SourceFormat, req.ToFormat)
				}
				return pluginapi.RequestInterceptResponse{Body: append(req.Body, []byte("|after")...)}, nil
			}),
		}},
	})

	got := host.InterceptRequestAfterAuth(context.Background(), pluginapi.RequestInterceptRequest{
		SourceFormat: "openai",
		ToFormat:     "codex",
		Model:        "gpt-5.4",
		Body:         []byte("body"),
	})

	if string(got.Body) != "body|after" {
		t.Fatalf("body = %q, want body|after", got.Body)
	}
}

func TestInterceptRequestPropagatesPath_Issue6196(t *testing.T) {
	host := newHostWithRecords(capabilityRecord{
		id: "path-rewriter",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
				return pluginapi.RequestInterceptResponse{
					Path: "/v1/images/generations",
					Body: []byte(`{"model":"gpt-image-2.5"}`),
				}, nil
			}),
		}},
	})

	got := host.InterceptRequestAfterAuth(context.Background(), pluginapi.RequestInterceptRequest{
		SourceFormat: "openai-image",
		Model:        "gpt-image-2.5",
		Body:         []byte(`{"model":"gpt-image-2.5"}`),
	})

	if got.Path != "/v1/images/generations" {
		t.Fatalf("got.Path = %q, want /v1/images/generations", got.Path)
	}
	if string(got.Body) != `{"model":"gpt-image-2.5"}` {
		t.Fatalf("got.Body = %s, want expected body", string(got.Body))
	}
}

func TestInterceptRequestPropagatesPathAcrossPluginChain_Issue6196(t *testing.T) {
	var secondPluginSeenPath any
	host := newHostWithRecords(
		capabilityRecord{
			id:       "first-path-rewriter",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					return pluginapi.RequestInterceptResponse{
						Path: "/v1/images/generations",
						Body: []byte(`{"model":"gpt-image-2.5","rewritten":true}`),
					}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "second-downstream-observer",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					if req.Metadata != nil {
						secondPluginSeenPath = req.Metadata[coreexecutor.RequestPathMetadataKey]
					}
					return pluginapi.RequestInterceptResponse{}, nil
				}),
			}},
		},
	)

	got := host.InterceptRequestAfterAuth(context.Background(), pluginapi.RequestInterceptRequest{
		SourceFormat: "openai-image",
		Model:        "gpt-image-2.5",
		Body:         []byte(`{"model":"gpt-image-2.5"}`),
		Metadata: map[string]any{
			coreexecutor.RequestPathMetadataKey: "/v1/images/edits",
		},
	})

	if got.Path != "/v1/images/generations" {
		t.Fatalf("got.Path = %q, want /v1/images/generations", got.Path)
	}
	if secondPluginSeenPath != "/v1/images/generations" {
		t.Fatalf("second plugin saw path = %v, want /v1/images/generations", secondPluginSeenPath)
	}
}

func TestInterceptorsSkipExceptedPlugin(t *testing.T) {
	originCalls := 0
	otherCalls := 0
	host := newHostWithRecords(
		capabilityRecord{
			id:       "origin",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					originCalls++
					return pluginapi.RequestInterceptResponse{Body: append(req.Body, []byte("|origin-request")...)}, nil
				}),
				ResponseInterceptor: responseInterceptorFunc{
					interceptResponse: func(ctx context.Context, req pluginapi.ResponseInterceptRequest) (pluginapi.ResponseInterceptResponse, error) {
						originCalls++
						return pluginapi.ResponseInterceptResponse{Body: append(req.Body, []byte("|origin-response")...)}, nil
					},
				},
				StreamChunkInterceptor: responseInterceptorFunc{
					interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
						originCalls++
						return pluginapi.StreamChunkInterceptResponse{Body: append(req.Body, []byte("|origin-stream")...)}, nil
					},
				},
			}},
		},
		capabilityRecord{
			id:       "other",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					otherCalls++
					return pluginapi.RequestInterceptResponse{Body: append(req.Body, []byte("|other-request")...)}, nil
				}),
				ResponseInterceptor: responseInterceptorFunc{
					interceptResponse: func(ctx context.Context, req pluginapi.ResponseInterceptRequest) (pluginapi.ResponseInterceptResponse, error) {
						otherCalls++
						return pluginapi.ResponseInterceptResponse{Body: append(req.Body, []byte("|other-response")...)}, nil
					},
				},
				StreamChunkInterceptor: responseInterceptorFunc{
					interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
						otherCalls++
						return pluginapi.StreamChunkInterceptResponse{Body: append(req.Body, []byte("|other-stream")...)}, nil
					},
				},
			}},
		},
	)

	reqOut := host.InterceptRequestBeforeAuthExcept(context.Background(), pluginapi.RequestInterceptRequest{Body: []byte("body")}, "origin")
	afterOut := host.InterceptRequestAfterAuthExcept(context.Background(), pluginapi.RequestInterceptRequest{Body: []byte("body")}, "origin")
	respOut := host.InterceptResponseExcept(context.Background(), pluginapi.ResponseInterceptRequest{Body: []byte("body")}, "origin")
	streamOut := host.InterceptStreamChunkExcept(context.Background(), pluginapi.StreamChunkInterceptRequest{Body: []byte("body")}, "origin")

	if originCalls != 0 {
		t.Fatalf("origin plugin calls = %d, want 0", originCalls)
	}
	if otherCalls != 4 {
		t.Fatalf("other plugin calls = %d, want 4", otherCalls)
	}
	if string(reqOut.Body) != "body|other-request" {
		t.Fatalf("request body = %q, want body|other-request", reqOut.Body)
	}
	if string(afterOut.Body) != "body|other-request" {
		t.Fatalf("after-auth request body = %q, want body|other-request", afterOut.Body)
	}
	if string(respOut.Body) != "body|other-response" {
		t.Fatalf("response body = %q, want body|other-response", respOut.Body)
	}
	if string(streamOut.Body) != "body|other-stream" {
		t.Fatalf("stream body = %q, want body|other-stream", streamOut.Body)
	}
}

func TestResponseInterceptorsChainAndStreamHistory(t *testing.T) {
	var seenHistory [][]byte
	var sawSecondResponse bool
	var sawSecondStream bool
	host := newHostWithRecords(
		capabilityRecord{
			id:       "high",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseInterceptor: responseInterceptorFunc{
					interceptResponse: func(ctx context.Context, req pluginapi.ResponseInterceptRequest) (pluginapi.ResponseInterceptResponse, error) {
						return pluginapi.ResponseInterceptResponse{
							Headers: http.Header{"X-Response": []string{"high"}},
							Body:    append(req.Body, []byte("|high")...),
						}, nil
					},
				},
				StreamChunkInterceptor: responseInterceptorFunc{
					interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
						seenHistory = req.HistoryChunks
						return pluginapi.StreamChunkInterceptResponse{
							Headers: http.Header{"X-Stream": []string{"high"}},
							Body:    append(req.Body, []byte("|high")...),
						}, nil
					},
				},
			}},
		},
		capabilityRecord{
			id:       "low",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseInterceptor: responseInterceptorFunc{
					interceptResponse: func(ctx context.Context, req pluginapi.ResponseInterceptRequest) (pluginapi.ResponseInterceptResponse, error) {
						if string(req.Body) != "body|high" {
							t.Fatalf("second response interceptor body = %q, want body|high", req.Body)
						}
						if req.ResponseHeaders.Get("X-Response") != "high" {
							t.Fatalf("second response interceptor headers = %#v, want high header", req.ResponseHeaders)
						}
						sawSecondResponse = true
						return pluginapi.ResponseInterceptResponse{
							Headers:      http.Header{"X-Response": []string{"low"}, "X-Low": []string{"1"}},
							ClearHeaders: []string{"X-Remove"},
							Body:         append(req.Body, []byte("|low")...),
						}, nil
					},
				},
				StreamChunkInterceptor: responseInterceptorFunc{
					interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
						if string(req.Body) != "chunk|high" {
							t.Fatalf("second stream interceptor body = %q, want chunk|high", req.Body)
						}
						if req.ResponseHeaders.Get("X-Stream") != "high" {
							t.Fatalf("second stream interceptor headers = %#v, want high header", req.ResponseHeaders)
						}
						if len(req.HistoryChunks) != 1 || string(req.HistoryChunks[0]) != "first" {
							t.Fatalf("second stream interceptor history = %#v", req.HistoryChunks)
						}
						seenHistory = req.HistoryChunks
						sawSecondStream = true
						return pluginapi.StreamChunkInterceptResponse{
							Headers:      http.Header{"X-Stream": []string{"low"}, "X-Low": []string{"1"}},
							ClearHeaders: []string{"X-Remove"},
							Body:         append(req.Body, []byte("|low")...),
						}, nil
					},
				},
			}},
		},
	)

	nonStream := host.InterceptResponse(context.Background(), pluginapi.ResponseInterceptRequest{
		SourceFormat:    "openai",
		Model:           "normalized",
		RequestedModel:  "requested",
		ResponseHeaders: http.Header{"Content-Type": []string{"application/json"}, "X-Remove": []string{"yes"}},
		Body:            []byte("body"),
		StatusCode:      http.StatusOK,
	})
	if string(nonStream.Body) != "body|high|low" || nonStream.Headers.Get("X-Response") != "low" || nonStream.Headers.Get("X-Low") != "1" {
		t.Fatalf("non-stream result = %#v", nonStream)
	}
	if nonStream.Headers.Get("X-Remove") != "" {
		t.Fatalf("non-stream headers kept cleared value: %#v", nonStream.Headers)
	}
	if !sawSecondResponse {
		t.Fatal("second response interceptor was not called")
	}

	stream := host.InterceptStreamChunk(context.Background(), pluginapi.StreamChunkInterceptRequest{
		SourceFormat:    "openai",
		Model:           "normalized",
		RequestedModel:  "requested",
		ResponseHeaders: http.Header{"Content-Type": []string{"text/event-stream"}, "X-Remove": []string{"yes"}},
		Body:            []byte("chunk"),
		HistoryChunks:   [][]byte{[]byte("first")},
		ChunkIndex:      1,
	})
	if string(stream.Body) != "chunk|high|low" || stream.Headers.Get("X-Stream") != "low" || stream.Headers.Get("X-Low") != "1" {
		t.Fatalf("stream result = %#v", stream)
	}
	if stream.Headers.Get("X-Remove") != "" {
		t.Fatalf("stream headers kept cleared value: %#v", stream.Headers)
	}
	if len(seenHistory) != 1 || string(seenHistory[0]) != "first" {
		t.Fatalf("history = %#v", seenHistory)
	}
	if !sawSecondStream {
		t.Fatal("second stream interceptor was not called")
	}
}

func TestInterceptorsSkipErrorsAndFusePanics(t *testing.T) {
	host := newHostWithRecords(
		capabilityRecord{
			id:       "error",
			priority: 30,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					return pluginapi.RequestInterceptResponse{}, fmt.Errorf("request failed")
				}),
			}},
		},
		capabilityRecord{
			id:       "panic",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					panic("request panic")
				}),
			}},
		},
		capabilityRecord{
			id:       "success",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					return pluginapi.RequestInterceptResponse{Body: append(req.Body, []byte("|success")...)}, nil
				}),
			}},
		},
	)

	got := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{Body: []byte("body")})
	if string(got.Body) != "body|success" {
		t.Fatalf("body = %q, want body|success", got.Body)
	}
	if !host.isPluginFused("panic") {
		t.Fatal("panic plugin was not fused")
	}
}

func TestStreamInterceptorsDropChunkStopsChain(t *testing.T) {
	var lowCalled bool
	host := newHostWithRecords(
		capabilityRecord{
			id:       "high",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				StreamChunkInterceptor: responseInterceptorFunc{
					interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
						return pluginapi.StreamChunkInterceptResponse{
							Headers:      http.Header{"X-Stream": []string{"high"}},
							Body:         append(req.Body, []byte("|high")...),
							DropChunk:    true,
							ClearHeaders: nil,
						}, nil
					},
				},
			}},
		},
		capabilityRecord{
			id:       "low",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				StreamChunkInterceptor: responseInterceptorFunc{
					interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
						lowCalled = true
						return pluginapi.StreamChunkInterceptResponse{
							Headers: http.Header{"X-Stream": []string{"low"}},
							Body:    append(req.Body, []byte("|low")...),
						}, nil
					},
				},
			}},
		},
	)

	got := host.InterceptStreamChunk(context.Background(), pluginapi.StreamChunkInterceptRequest{
		SourceFormat:   "openai",
		Model:          "normalized",
		RequestedModel: "requested",
		Body:           []byte("chunk"),
	})
	if lowCalled {
		t.Fatal("low-priority stream interceptor should not be called after DropChunk")
	}
	if !got.DropChunk {
		t.Fatal("DropChunk = false, want true")
	}
	if string(got.Body) != "chunk|high" {
		t.Fatalf("body = %q, want chunk|high", got.Body)
	}
	if got.Headers.Get("X-Stream") != "high" {
		t.Fatalf("headers = %#v, want high header", got.Headers)
	}
}

func TestHasStreamInterceptorsReflectsActiveStreamInterceptors(t *testing.T) {
	requestOnly := newHostWithRecords(capabilityRecord{
		id: "request",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
				return pluginapi.RequestInterceptResponse{Body: req.Body}, nil
			}),
		}},
	})
	if requestOnly.HasStreamInterceptors() {
		t.Fatal("HasStreamInterceptors() = true, want false for request-only plugins")
	}

	responseOnly := newHostWithRecords(capabilityRecord{
		id: "response",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ResponseInterceptor: responseInterceptorFunc{
				interceptResponse: func(ctx context.Context, req pluginapi.ResponseInterceptRequest) (pluginapi.ResponseInterceptResponse, error) {
					return pluginapi.ResponseInterceptResponse{Body: req.Body}, nil
				},
			},
		}},
	})
	if responseOnly.HasStreamInterceptors() {
		t.Fatal("HasStreamInterceptors() = true, want false for response-only plugins")
	}

	streamHost := newHostWithRecords(capabilityRecord{
		id: "stream",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			StreamChunkInterceptor: responseInterceptorFunc{
				interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
					return pluginapi.StreamChunkInterceptResponse{Body: req.Body}, nil
				},
			},
		}},
	})
	if !streamHost.HasStreamInterceptors() {
		t.Fatal("HasStreamInterceptors() = false, want true for stream interceptors")
	}
	streamHost.mu.Lock()
	streamHost.fused["stream"] = "test fused"
	streamHost.mu.Unlock()
	if streamHost.HasStreamInterceptors() {
		t.Fatal("HasStreamInterceptors() = true, want false after interceptor plugin is fused")
	}
}

func TestStreamChunkRequestBodyPolicyBySchemaVersion(t *testing.T) {
	var legacyGot, modernGot pluginapi.StreamChunkInterceptRequest
	host := newHostWithRecords(
		capabilityRecord{
			id: "legacy",
			plugin: pluginapi.Plugin{
				SchemaVersion: 2,
				Capabilities: pluginapi.Capabilities{
					StreamChunkInterceptor: responseInterceptorFunc{
						interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
							legacyGot = req
							return pluginapi.StreamChunkInterceptResponse{Body: req.Body}, nil
						},
					},
				},
			},
		},
		capabilityRecord{
			id: "modern",
			plugin: pluginapi.Plugin{
				SchemaVersion: pluginabi.SchemaVersionStreamChunkOmitRequestBody,
				Capabilities: pluginapi.Capabilities{
					StreamChunkInterceptor: responseInterceptorFunc{
						interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
							modernGot = req
							return pluginapi.StreamChunkInterceptResponse{Body: req.Body}, nil
						},
					},
				},
			},
		},
	)
	if !host.StreamChunkPayloadIncludesRequestBody() {
		t.Fatal("StreamChunkPayloadIncludesRequestBody() = false, want true when legacy stream interceptor is active")
	}

	_ = host.InterceptStreamChunk(context.Background(), pluginapi.StreamChunkInterceptRequest{
		OriginalRequest: []byte("original"),
		RequestBody:     []byte("request"),
		Body:            []byte("chunk"),
		ChunkIndex:      0,
	})
	if string(legacyGot.OriginalRequest) != "original" || string(legacyGot.RequestBody) != "request" {
		t.Fatalf("legacy payload bodies = original:%q body:%q, want preserved", legacyGot.OriginalRequest, legacyGot.RequestBody)
	}
	if len(modernGot.OriginalRequest) != 0 || len(modernGot.RequestBody) != 0 {
		t.Fatalf("modern payload bodies = original:%q body:%q, want omitted", modernGot.OriginalRequest, modernGot.RequestBody)
	}

	legacyGot = pluginapi.StreamChunkInterceptRequest{}
	modernGot = pluginapi.StreamChunkInterceptRequest{}
	_ = host.InterceptStreamChunk(context.Background(), pluginapi.StreamChunkInterceptRequest{
		OriginalRequest: []byte("original"),
		RequestBody:     []byte("request"),
		ChunkIndex:      pluginapi.StreamChunkHeaderInitIndex,
	})
	if string(legacyGot.OriginalRequest) != "original" || string(modernGot.OriginalRequest) != "original" {
		t.Fatalf("header-init bodies not preserved: legacy=%q modern=%q", legacyGot.OriginalRequest, modernGot.OriginalRequest)
	}

	modernOnly := newHostWithRecords(capabilityRecord{
		id: "modern-only",
		plugin: pluginapi.Plugin{
			SchemaVersion: pluginabi.SchemaVersionStreamChunkOmitRequestBody,
			Capabilities: pluginapi.Capabilities{
				StreamChunkInterceptor: responseInterceptorFunc{
					interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
						return pluginapi.StreamChunkInterceptResponse{}, nil
					},
				},
			},
		},
	})
	if modernOnly.StreamChunkPayloadIncludesRequestBody() {
		t.Fatal("StreamChunkPayloadIncludesRequestBody() = true, want false for schema v3+ only")
	}
}

func TestStreamChunkHistoryPolicyBySchemaVersion(t *testing.T) {
	var legacyGot, modernGot pluginapi.StreamChunkInterceptRequest
	host := newHostWithRecords(
		capabilityRecord{
			id: "legacy",
			plugin: pluginapi.Plugin{
				SchemaVersion: 4,
				Capabilities: pluginapi.Capabilities{
					StreamChunkInterceptor: responseInterceptorFunc{
						interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
							legacyGot = req
							return pluginapi.StreamChunkInterceptResponse{Body: req.Body}, nil
						},
					},
				},
			},
		},
		capabilityRecord{
			id: "modern",
			plugin: pluginapi.Plugin{
				SchemaVersion: pluginabi.SchemaVersionStreamChunkOmitHistory,
				Capabilities: pluginapi.Capabilities{
					StreamChunkInterceptor: responseInterceptorFunc{
						interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
							modernGot = req
							return pluginapi.StreamChunkInterceptResponse{Body: req.Body}, nil
						},
					},
				},
			},
		},
	)
	if !host.StreamChunkPayloadIncludesHistory() {
		t.Fatal("StreamChunkPayloadIncludesHistory() = false, want true when legacy stream interceptor is active")
	}

	_ = host.InterceptStreamChunk(context.Background(), pluginapi.StreamChunkInterceptRequest{
		HistoryChunks: [][]byte{[]byte("first")},
		Body:          []byte("chunk"),
		ChunkIndex:    0,
	})
	if len(legacyGot.HistoryChunks) != 1 || string(legacyGot.HistoryChunks[0]) != "first" {
		t.Fatalf("legacy payload history = %#v, want preserved", legacyGot.HistoryChunks)
	}
	if len(modernGot.HistoryChunks) != 0 {
		t.Fatalf("modern payload history = %#v, want omitted", modernGot.HistoryChunks)
	}

	legacyGot = pluginapi.StreamChunkInterceptRequest{}
	modernGot = pluginapi.StreamChunkInterceptRequest{}
	_ = host.InterceptStreamChunk(context.Background(), pluginapi.StreamChunkInterceptRequest{
		HistoryChunks: [][]byte{[]byte("first")},
		Body:          []byte("chunk"),
		ChunkIndex:    pluginapi.StreamChunkHeaderInitIndex,
	})
	if len(legacyGot.HistoryChunks) != 1 || string(legacyGot.HistoryChunks[0]) != "first" {
		t.Fatalf("legacy init history = %#v, want preserved", legacyGot.HistoryChunks)
	}
	if len(modernGot.HistoryChunks) != 1 || string(modernGot.HistoryChunks[0]) != "first" {
		t.Fatalf("modern init history = %#v, want preserved", modernGot.HistoryChunks)
	}

	modernOnly := newHostWithRecords(capabilityRecord{
		id: "modern-only",
		plugin: pluginapi.Plugin{
			SchemaVersion: pluginabi.SchemaVersionStreamChunkOmitHistory,
			Capabilities: pluginapi.Capabilities{
				StreamChunkInterceptor: responseInterceptorFunc{
					interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
						return pluginapi.StreamChunkInterceptResponse{}, nil
					},
				},
			},
		},
	})
	if modernOnly.StreamChunkPayloadIncludesHistory() {
		t.Fatal("StreamChunkPayloadIncludesHistory() = true, want false for schema v5+ only")
	}
}

func TestRequestInterceptorBodyOmittedForSchema7PluginsThatOptOut(t *testing.T) {
	var omitGot, bodyGot, legacyGot pluginapi.RequestInterceptRequest
	var omitCalls, bodyCalls, legacyCalls int
	newInterceptor := func(got *pluginapi.RequestInterceptRequest, calls *int) requestInterceptorFunc {
		return requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
			*calls++
			*got = req
			return pluginapi.RequestInterceptResponse{}, nil
		})
	}
	host := newHostWithRecords(
		capabilityRecord{
			id: "omit",
			plugin: pluginapi.Plugin{
				SchemaVersion: pluginabi.SchemaVersionRequestInterceptorOmitBody,
				Capabilities: pluginapi.Capabilities{
					RequestInterceptor:               newInterceptor(&omitGot, &omitCalls),
					RequestInterceptorOmitBodyBefore: true,
					RequestInterceptorOmitBodyAfter:  true,
				},
			},
		},
		capabilityRecord{
			id: "body",
			plugin: pluginapi.Plugin{
				SchemaVersion: pluginabi.SchemaVersionRequestInterceptorOmitBody,
				Capabilities: pluginapi.Capabilities{
					RequestInterceptor: newInterceptor(&bodyGot, &bodyCalls),
				},
			},
		},
		capabilityRecord{
			id: "legacy",
			plugin: pluginapi.Plugin{
				SchemaVersion: 6,
				Capabilities: pluginapi.Capabilities{
					RequestInterceptor:               newInterceptor(&legacyGot, &legacyCalls),
					RequestInterceptorOmitBodyBefore: true,
					RequestInterceptorOmitBodyAfter:  true,
				},
			},
		},
	)

	_ = host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{Body: []byte("request")})
	if omitCalls != 1 || bodyCalls != 1 || legacyCalls != 1 {
		t.Fatalf("before calls = omit:%d body:%d legacy:%d, want 1 each", omitCalls, bodyCalls, legacyCalls)
	}
	if len(omitGot.Body) != 0 {
		t.Fatalf("omit plugin before body = %q, want omitted", omitGot.Body)
	}
	if string(bodyGot.Body) != "request" {
		t.Fatalf("body plugin before body = %q, want preserved", bodyGot.Body)
	}
	if string(legacyGot.Body) != "request" {
		t.Fatalf("legacy plugin before body = %q, want preserved on schema < 7", legacyGot.Body)
	}

	omitGot = pluginapi.RequestInterceptRequest{}
	bodyGot = pluginapi.RequestInterceptRequest{}
	legacyGot = pluginapi.RequestInterceptRequest{}
	_ = host.InterceptRequestAfterAuth(context.Background(), pluginapi.RequestInterceptRequest{Body: []byte("request")})
	if omitCalls != 2 || bodyCalls != 2 || legacyCalls != 2 {
		t.Fatalf("after calls = omit:%d body:%d legacy:%d, want 2 each", omitCalls, bodyCalls, legacyCalls)
	}
	if len(omitGot.Body) != 0 {
		t.Fatalf("omit plugin after body = %q, want omitted", omitGot.Body)
	}
	if string(bodyGot.Body) != "request" {
		t.Fatalf("body plugin after body = %q, want preserved", bodyGot.Body)
	}
	if string(legacyGot.Body) != "request" {
		t.Fatalf("legacy plugin after body = %q, want preserved on schema < 7", legacyGot.Body)
	}
}

func TestRequestInterceptorBodyOmitIsPerPhase(t *testing.T) {
	for _, tc := range []struct {
		name       string
		before     bool
		after      bool
		wantBefore int
		wantAfter  int
	}{
		{name: "before only", before: true, wantBefore: 0, wantAfter: len("request")},
		{name: "after only", after: true, wantBefore: len("request"), wantAfter: 0},
	} {
		t.Run(tc.name, func(t *testing.T) {
			var got pluginapi.RequestInterceptRequest
			calls := 0
			host := newHostWithRecords(capabilityRecord{
				id: "phase",
				plugin: pluginapi.Plugin{
					SchemaVersion: pluginabi.SchemaVersionRequestInterceptorOmitBody,
					Capabilities: pluginapi.Capabilities{
						RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
							calls++
							got = req
							return pluginapi.RequestInterceptResponse{}, nil
						}),
						RequestInterceptorOmitBodyBefore: tc.before,
						RequestInterceptorOmitBodyAfter:  tc.after,
					},
				},
			})
			_ = host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{Body: []byte("request")})
			if calls != 1 {
				t.Fatalf("before calls = %d, want 1", calls)
			}
			if len(got.Body) != tc.wantBefore {
				t.Fatalf("before body len = %d, want %d", len(got.Body), tc.wantBefore)
			}
			got = pluginapi.RequestInterceptRequest{}
			_ = host.InterceptRequestAfterAuth(context.Background(), pluginapi.RequestInterceptRequest{Body: []byte("request")})
			if calls != 2 {
				t.Fatalf("after calls = %d, want 2", calls)
			}
			if len(got.Body) != tc.wantAfter {
				t.Fatalf("after body len = %d, want %d", len(got.Body), tc.wantAfter)
			}
		})
	}
}

func TestRequestInterceptorOmitPluginCanReplaceBodyBlindly(t *testing.T) {
	var readerGot pluginapi.RequestInterceptRequest
	host := newHostWithRecords(
		capabilityRecord{
			id: "a-omit",
			plugin: pluginapi.Plugin{
				SchemaVersion: pluginabi.SchemaVersionRequestInterceptorOmitBody,
				Capabilities: pluginapi.Capabilities{
					RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
						if len(req.Body) != 0 {
							t.Errorf("omit plugin body = %q, want omitted", req.Body)
						}
						return pluginapi.RequestInterceptResponse{Body: []byte("replaced")}, nil
					}),
					RequestInterceptorOmitBodyBefore: true,
					RequestInterceptorOmitBodyAfter:  true,
				},
			},
		},
		capabilityRecord{
			id: "b-reader",
			plugin: pluginapi.Plugin{
				SchemaVersion: pluginabi.SchemaVersionRequestInterceptorOmitBody,
				Capabilities: pluginapi.Capabilities{
					RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
						readerGot = req
						return pluginapi.RequestInterceptResponse{}, nil
					}),
				},
			},
		},
	)
	resp := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{Body: []byte("request")})
	if string(readerGot.Body) != "replaced" {
		t.Fatalf("reader body = %q, want replaced body from omit plugin", readerGot.Body)
	}
	if string(resp.Body) != "replaced" {
		t.Fatalf("host response body = %q, want replaced", resp.Body)
	}
}

func TestRequestInterceptorOmitPluginCanTerminate(t *testing.T) {
	readerCalls := 0
	host := newHostWithRecords(
		capabilityRecord{
			id: "a-omit",
			plugin: pluginapi.Plugin{
				SchemaVersion: pluginabi.SchemaVersionRequestInterceptorOmitBody,
				Capabilities: pluginapi.Capabilities{
					RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
						return pluginapi.RequestInterceptResponse{Terminate: true, StatusCode: 403}, nil
					}),
					RequestInterceptorOmitBodyBefore: true,
					RequestInterceptorOmitBodyAfter:  true,
				},
			},
		},
		capabilityRecord{
			id: "b-reader",
			plugin: pluginapi.Plugin{
				SchemaVersion: pluginabi.SchemaVersionRequestInterceptorOmitBody,
				Capabilities: pluginapi.Capabilities{
					RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
						readerCalls++
						return pluginapi.RequestInterceptResponse{}, nil
					}),
				},
			},
		},
	)
	resp := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{Body: []byte("request")})
	if !resp.Terminate || resp.StatusCode != 403 {
		t.Fatalf("resp = %#v, want terminated with 403", resp)
	}
	if readerCalls != 0 {
		t.Fatalf("reader calls = %d, want 0 after termination", readerCalls)
	}
}

func TestHasRequestInterceptorsReflectsActiveRequestInterceptors(t *testing.T) {
	responseOnly := newHostWithRecords(capabilityRecord{
		id: "response",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ResponseInterceptor: responseInterceptorFunc{
				interceptResponse: func(ctx context.Context, req pluginapi.ResponseInterceptRequest) (pluginapi.ResponseInterceptResponse, error) {
					return pluginapi.ResponseInterceptResponse{Body: req.Body}, nil
				},
			},
		}},
	})
	if responseOnly.HasRequestInterceptors() {
		t.Fatal("HasRequestInterceptors() = true, want false for response-only plugins")
	}

	requestHost := newHostWithRecords(capabilityRecord{
		id: "request",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
				return pluginapi.RequestInterceptResponse{Body: req.Body}, nil
			}),
		}},
	})
	if !requestHost.HasRequestInterceptors() {
		t.Fatal("HasRequestInterceptors() = false, want true for request interceptors")
	}
	requestHost.mu.Lock()
	requestHost.fused["request"] = "test fused"
	requestHost.mu.Unlock()
	if requestHost.HasRequestInterceptors() {
		t.Fatal("HasRequestInterceptors() = true, want false after request plugin is fused")
	}
}

func TestInterceptorsDoNotMutateInputs(t *testing.T) {
	t.Run("request", func(t *testing.T) {
		headers := http.Header{"X-Request": []string{"input"}}
		metadata := map[string]any{
			"nested":    map[string]any{"value": "original"},
			"items":     []any{map[string]any{"value": "original"}},
			"strings":   []string{"original"},
			"bytes":     []byte("original"),
			"labels":    map[string]string{"name": "original"},
			"values":    url.Values{"name": []string{"original"}},
			"mapSlice":  map[string][]string{"name": []string{"original"}},
			"sliceMap":  []map[string]string{{"name": "original"}},
			"aliasMap":  stringSliceAlias{"original"},
			"aliasList": mapSliceAlias{{"name": "original"}},
			"key":       "value",
		}
		body := []byte("request-body")
		host := newHostWithRecords(capabilityRecord{
			id: "request",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					req.Headers.Set("X-Request", "mutated")
					req.Body[0] = 'R'
					req.Metadata["key"] = "mutated"
					req.Metadata["nested"].(map[string]any)["value"] = "mutated"
					req.Metadata["items"].([]any)[0].(map[string]any)["value"] = "mutated"
					req.Metadata["strings"].([]string)[0] = "mutated"
					req.Metadata["bytes"].([]byte)[0] = 'M'
					req.Metadata["labels"].(map[string]string)["name"] = "mutated"
					req.Metadata["values"].(url.Values)["name"][0] = "mutated"
					req.Metadata["mapSlice"].(map[string][]string)["name"][0] = "mutated"
					req.Metadata["sliceMap"].([]map[string]string)[0]["name"] = "mutated"
					req.Metadata["aliasMap"].(stringSliceAlias)[0] = "mutated"
					req.Metadata["aliasList"].(mapSliceAlias)[0]["name"] = "mutated"
					return pluginapi.RequestInterceptResponse{Body: append(req.Body, []byte("|ok")...)}, nil
				}),
			}},
		})

		got := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{
			Headers:  headers,
			Body:     body,
			Metadata: metadata,
		})
		if headers.Get("X-Request") != "input" {
			t.Fatalf("request headers mutated: %#v", headers)
		}
		if string(body) != "request-body" {
			t.Fatalf("request body mutated: %q", body)
		}
		if metadata["key"] != "value" {
			t.Fatalf("request metadata mutated: %#v", metadata)
		}
		if metadata["nested"].(map[string]any)["value"] != "original" || metadata["items"].([]any)[0].(map[string]any)["value"] != "original" {
			t.Fatalf("request nested metadata mutated: %#v", metadata)
		}
		if metadata["strings"].([]string)[0] != "original" || string(metadata["bytes"].([]byte)) != "original" || metadata["labels"].(map[string]string)["name"] != "original" {
			t.Fatalf("request nested metadata aliases mutated: %#v", metadata)
		}
		if metadata["values"].(url.Values)["name"][0] != "original" || metadata["mapSlice"].(map[string][]string)["name"][0] != "original" {
			t.Fatalf("request map/slice metadata mutated: %#v", metadata)
		}
		if metadata["sliceMap"].([]map[string]string)[0]["name"] != "original" || metadata["aliasMap"].(stringSliceAlias)[0] != "original" || metadata["aliasList"].(mapSliceAlias)[0]["name"] != "original" {
			t.Fatalf("request alias metadata mutated: %#v", metadata)
		}
		if !strings.HasSuffix(string(got.Body), "|ok") {
			t.Fatalf("request result body = %q", got.Body)
		}
	})

	t.Run("response", func(t *testing.T) {
		requestHeaders := http.Header{"X-Request": []string{"input"}}
		responseHeaders := http.Header{"X-Response": []string{"input"}}
		originalRequest := []byte("original")
		requestBody := []byte("request")
		body := []byte("body")
		metadata := map[string]any{
			"nested":    map[string]any{"value": "original"},
			"items":     []any{map[string]any{"value": "original"}},
			"strings":   []string{"original"},
			"bytes":     []byte("original"),
			"labels":    map[string]string{"name": "original"},
			"values":    url.Values{"name": []string{"original"}},
			"mapSlice":  map[string][]string{"name": []string{"original"}},
			"sliceMap":  []map[string]string{{"name": "original"}},
			"aliasMap":  stringSliceAlias{"original"},
			"aliasList": mapSliceAlias{{"name": "original"}},
			"key":       "value",
		}
		host := newHostWithRecords(capabilityRecord{
			id: "response",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseInterceptor: responseInterceptorFunc{
					interceptResponse: func(ctx context.Context, req pluginapi.ResponseInterceptRequest) (pluginapi.ResponseInterceptResponse, error) {
						req.RequestHeaders.Set("X-Request", "mutated")
						req.ResponseHeaders.Set("X-Response", "mutated")
						req.OriginalRequest[0] = 'O'
						req.RequestBody[0] = 'R'
						req.Body[0] = 'B'
						req.Metadata["key"] = "mutated"
						req.Metadata["nested"].(map[string]any)["value"] = "mutated"
						req.Metadata["items"].([]any)[0].(map[string]any)["value"] = "mutated"
						req.Metadata["strings"].([]string)[0] = "mutated"
						req.Metadata["bytes"].([]byte)[0] = 'M'
						req.Metadata["labels"].(map[string]string)["name"] = "mutated"
						req.Metadata["values"].(url.Values)["name"][0] = "mutated"
						req.Metadata["mapSlice"].(map[string][]string)["name"][0] = "mutated"
						req.Metadata["sliceMap"].([]map[string]string)[0]["name"] = "mutated"
						req.Metadata["aliasMap"].(stringSliceAlias)[0] = "mutated"
						req.Metadata["aliasList"].(mapSliceAlias)[0]["name"] = "mutated"
						return pluginapi.ResponseInterceptResponse{Body: append(req.Body, []byte("|ok")...)}, nil
					},
				},
			}},
		})

		got := host.InterceptResponse(context.Background(), pluginapi.ResponseInterceptRequest{
			RequestHeaders:  requestHeaders,
			ResponseHeaders: responseHeaders,
			OriginalRequest: originalRequest,
			RequestBody:     requestBody,
			Body:            body,
			Metadata:        metadata,
		})
		if requestHeaders.Get("X-Request") != "input" {
			t.Fatalf("request headers mutated: %#v", requestHeaders)
		}
		if responseHeaders.Get("X-Response") != "input" {
			t.Fatalf("response headers mutated: %#v", responseHeaders)
		}
		if string(originalRequest) != "original" {
			t.Fatalf("original request mutated: %q", originalRequest)
		}
		if string(requestBody) != "request" {
			t.Fatalf("request body mutated: %q", requestBody)
		}
		if string(body) != "body" {
			t.Fatalf("response body mutated: %q", body)
		}
		if metadata["key"] != "value" {
			t.Fatalf("response metadata mutated: %#v", metadata)
		}
		if metadata["nested"].(map[string]any)["value"] != "original" || metadata["items"].([]any)[0].(map[string]any)["value"] != "original" {
			t.Fatalf("response nested metadata mutated: %#v", metadata)
		}
		if metadata["strings"].([]string)[0] != "original" || string(metadata["bytes"].([]byte)) != "original" || metadata["labels"].(map[string]string)["name"] != "original" {
			t.Fatalf("response nested metadata aliases mutated: %#v", metadata)
		}
		if metadata["values"].(url.Values)["name"][0] != "original" || metadata["mapSlice"].(map[string][]string)["name"][0] != "original" {
			t.Fatalf("response map/slice metadata mutated: %#v", metadata)
		}
		if metadata["sliceMap"].([]map[string]string)[0]["name"] != "original" || metadata["aliasMap"].(stringSliceAlias)[0] != "original" || metadata["aliasList"].(mapSliceAlias)[0]["name"] != "original" {
			t.Fatalf("response alias metadata mutated: %#v", metadata)
		}
		if !strings.HasSuffix(string(got.Body), "|ok") {
			t.Fatalf("response result body = %q", got.Body)
		}
	})

	t.Run("stream", func(t *testing.T) {
		requestHeaders := http.Header{"X-Request": []string{"input"}}
		responseHeaders := http.Header{"X-Response": []string{"input"}}
		originalRequest := []byte("original")
		requestBody := []byte("request")
		body := []byte("chunk")
		history := [][]byte{[]byte("first")}
		metadata := map[string]any{
			"nested":    map[string]any{"value": "original"},
			"items":     []any{map[string]any{"value": "original"}},
			"strings":   []string{"original"},
			"bytes":     []byte("original"),
			"labels":    map[string]string{"name": "original"},
			"values":    url.Values{"name": []string{"original"}},
			"mapSlice":  map[string][]string{"name": []string{"original"}},
			"sliceMap":  []map[string]string{{"name": "original"}},
			"aliasMap":  stringSliceAlias{"original"},
			"aliasList": mapSliceAlias{{"name": "original"}},
			"key":       "value",
		}
		host := newHostWithRecords(capabilityRecord{
			id: "stream",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				StreamChunkInterceptor: responseInterceptorFunc{
					interceptStreamChunk: func(ctx context.Context, req pluginapi.StreamChunkInterceptRequest) (pluginapi.StreamChunkInterceptResponse, error) {
						req.RequestHeaders.Set("X-Request", "mutated")
						req.ResponseHeaders.Set("X-Response", "mutated")
						req.OriginalRequest[0] = 'O'
						req.RequestBody[0] = 'R'
						req.Body[0] = 'C'
						req.HistoryChunks[0][0] = 'F'
						req.Metadata["key"] = "mutated"
						req.Metadata["nested"].(map[string]any)["value"] = "mutated"
						req.Metadata["items"].([]any)[0].(map[string]any)["value"] = "mutated"
						req.Metadata["strings"].([]string)[0] = "mutated"
						req.Metadata["bytes"].([]byte)[0] = 'M'
						req.Metadata["labels"].(map[string]string)["name"] = "mutated"
						req.Metadata["values"].(url.Values)["name"][0] = "mutated"
						req.Metadata["mapSlice"].(map[string][]string)["name"][0] = "mutated"
						req.Metadata["sliceMap"].([]map[string]string)[0]["name"] = "mutated"
						req.Metadata["aliasMap"].(stringSliceAlias)[0] = "mutated"
						req.Metadata["aliasList"].(mapSliceAlias)[0]["name"] = "mutated"
						return pluginapi.StreamChunkInterceptResponse{Body: append(req.Body, []byte("|ok")...)}, nil
					},
				},
			}},
		})

		got := host.InterceptStreamChunk(context.Background(), pluginapi.StreamChunkInterceptRequest{
			RequestHeaders:  requestHeaders,
			ResponseHeaders: responseHeaders,
			OriginalRequest: originalRequest,
			RequestBody:     requestBody,
			Body:            body,
			HistoryChunks:   history,
			Metadata:        metadata,
		})
		if requestHeaders.Get("X-Request") != "input" {
			t.Fatalf("request headers mutated: %#v", requestHeaders)
		}
		if responseHeaders.Get("X-Response") != "input" {
			t.Fatalf("response headers mutated: %#v", responseHeaders)
		}
		if string(originalRequest) != "original" {
			t.Fatalf("original request mutated: %q", originalRequest)
		}
		if string(requestBody) != "request" {
			t.Fatalf("request body mutated: %q", requestBody)
		}
		if string(body) != "chunk" {
			t.Fatalf("stream body mutated: %q", body)
		}
		if string(history[0]) != "first" {
			t.Fatalf("history mutated: %#v", history)
		}
		if metadata["key"] != "value" {
			t.Fatalf("stream metadata mutated: %#v", metadata)
		}
		if metadata["nested"].(map[string]any)["value"] != "original" || metadata["items"].([]any)[0].(map[string]any)["value"] != "original" {
			t.Fatalf("stream nested metadata mutated: %#v", metadata)
		}
		if metadata["strings"].([]string)[0] != "original" || string(metadata["bytes"].([]byte)) != "original" || metadata["labels"].(map[string]string)["name"] != "original" {
			t.Fatalf("stream nested metadata aliases mutated: %#v", metadata)
		}
		if metadata["values"].(url.Values)["name"][0] != "original" || metadata["mapSlice"].(map[string][]string)["name"][0] != "original" {
			t.Fatalf("stream map/slice metadata mutated: %#v", metadata)
		}
		if metadata["sliceMap"].([]map[string]string)[0]["name"] != "original" || metadata["aliasMap"].(stringSliceAlias)[0] != "original" || metadata["aliasList"].(mapSliceAlias)[0]["name"] != "original" {
			t.Fatalf("stream alias metadata mutated: %#v", metadata)
		}
		if !strings.HasSuffix(string(got.Body), "|ok") {
			t.Fatalf("stream result body = %q", got.Body)
		}
	})

	t.Run("pointers-and-cycle", func(t *testing.T) {
		type pointerMetadata struct {
			Value string
			Items []string
		}

		structValue := &pointerMetadata{Value: "original", Items: []string{"original"}}
		mapValue := &map[string][]string{"names": []string{"original"}}
		sliceValue := &[]string{"original"}
		aliasMapValue := &mapSliceAlias{{"name": "original"}}
		var ifaceValue any = &pointerMetadata{Value: "original", Items: []string{"original"}}
		cycle := map[string]any{}
		cycle["self"] = cycle

		metadata := map[string]any{
			"struct_ptr": structValue,
			"map_ptr":    mapValue,
			"slice_ptr":  sliceValue,
			"alias_ptr":  aliasMapValue,
			"iface_ptr":  ifaceValue,
			"cycle":      cycle,
		}

		host := newHostWithRecords(capabilityRecord{
			id: "pointer",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					req.Metadata["struct_ptr"].(*pointerMetadata).Value = "mutated"
					req.Metadata["struct_ptr"].(*pointerMetadata).Items[0] = "mutated"
					(*req.Metadata["map_ptr"].(*map[string][]string))["names"][0] = "mutated"
					(*req.Metadata["slice_ptr"].(*[]string))[0] = "mutated"
					(*req.Metadata["alias_ptr"].(*mapSliceAlias))[0]["name"] = "mutated"
					req.Metadata["iface_ptr"].(*pointerMetadata).Value = "mutated"
					if clonedCycle, ok := req.Metadata["cycle"].(map[string]any); ok {
						clonedCycle["marker"] = "mutated"
						clonedCycle["self"] = "mutated"
					}
					return pluginapi.RequestInterceptResponse{Body: []byte("ok")}, nil
				}),
			}},
		})

		_ = host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{Metadata: metadata})

		if structValue.Value != "original" || structValue.Items[0] != "original" {
			t.Fatalf("struct pointer metadata mutated: %#v", structValue)
		}
		if (*mapValue)["names"][0] != "original" {
			t.Fatalf("map pointer metadata mutated: %#v", mapValue)
		}
		if (*sliceValue)[0] != "original" {
			t.Fatalf("slice pointer metadata mutated: %#v", sliceValue)
		}
		if (*aliasMapValue)[0]["name"] != "original" {
			t.Fatalf("alias pointer metadata mutated: %#v", aliasMapValue)
		}
		if ifaceStruct, ok := ifaceValue.(*pointerMetadata); !ok || ifaceStruct.Value != "original" || ifaceStruct.Items[0] != "original" {
			t.Fatalf("interface pointer metadata mutated: %#v", ifaceValue)
		}
		if _, ok := cycle["self"].(map[string]any); !ok {
			t.Fatalf("cycle metadata structure changed unexpectedly: %#v", cycle)
		}
		if _, ok := cycle["marker"]; ok {
			t.Fatalf("cycle metadata mutated: %#v", cycle)
		}
	})
}

func TestResponseHooksKeepPayloadOrTryNextOnErrorAndEmptyBody(t *testing.T) {
	normalizerHost := newHostWithRecords(
		capabilityRecord{
			id:       "before-error",
			priority: 30,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseBeforeTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, fmt.Errorf("before failed")
				}),
				ResponseAfterTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, fmt.Errorf("after failed")
				}),
			}},
		},
		capabilityRecord{
			id:       "before-empty",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseBeforeTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, nil
				}),
				ResponseAfterTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "before-success",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseBeforeTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: []byte("before-success")}, nil
				}),
				ResponseAfterTranslator: responseNormalizerFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: []byte("after-success")}, nil
				}),
			}},
		},
	)

	before := normalizerHost.NormalizeResponseBefore(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", nil, nil, []byte("original"), false)
	if string(before) != "before-success" {
		t.Fatalf("NormalizeResponseBefore() = %q, want %q", before, "before-success")
	}
	after := normalizerHost.NormalizeResponseAfter(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", nil, nil, []byte("original"), false)
	if string(after) != "after-success" {
		t.Fatalf("NormalizeResponseAfter() = %q, want %q", after, "after-success")
	}

	translatorHost := newHostWithRecords(
		capabilityRecord{
			id:       "translator-error",
			priority: 30,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseTranslator: responseTranslatorFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, fmt.Errorf("translate failed")
				}),
			}},
		},
		capabilityRecord{
			id:       "translator-empty",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseTranslator: responseTranslatorFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "translator-success",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseTranslator: responseTranslatorFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					return pluginapi.PayloadResponse{Body: []byte("response-translated")}, nil
				}),
			}},
		},
	)

	translated, ok := translatorHost.TranslateResponse(context.Background(), sdktranslator.FormatOpenAI, sdktranslator.FormatClaude, "model", nil, nil, []byte("original"), false)
	if !ok {
		t.Fatal("TranslateResponse() ok = false, want true")
	}
	if string(translated) != "response-translated" {
		t.Fatalf("TranslateResponse() = %q, want %q", translated, "response-translated")
	}
}

func TestUsageAdapterPanicFusesPlugin(t *testing.T) {
	host := newHostWithRecords(capabilityRecord{
		id: "usage-panic",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
				panic("usage panic")
			}),
		}},
	})
	adapter := &usageAdapter{
		host:     host,
		pluginID: "usage-panic",
	}

	adapter.HandleUsage(context.Background(), coreusage.Record{Provider: "plugin-provider"})
	if !host.isPluginFused("usage-panic") {
		t.Fatal("usage-panic was not fused")
	}
}

func TestUsageAdapterNormalizesOmittedGenerateToTrue(t *testing.T) {
	var gotGenerate bool
	plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		gotGenerate = record.Generate
	})
	host := newHostWithRecords(capabilityRecord{
		id: "usage-generate",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: plugin,
		}},
	})
	adapter := &usageAdapter{
		host:     host,
		pluginID: "usage-generate",
	}

	// Legacy callers construct usage.Record without Generate; adapter must publish true.
	adapter.HandleUsage(context.Background(), coreusage.Record{Provider: "provider", Model: "gpt-5.4"})
	if !gotGenerate {
		t.Fatalf("plugin Generate = %v, want true for omitted field", gotGenerate)
	}
}

func TestUsageAdapterPropagatesBaseURL(t *testing.T) {
	var gotBaseURL string
	plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		gotBaseURL = record.BaseURL
	})
	host := newHostWithRecords(capabilityRecord{
		id: "usage-base-url",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: plugin,
		}},
	})
	adapter := &usageAdapter{
		host:     host,
		pluginID: "usage-base-url",
	}

	adapter.HandleUsage(context.Background(), coreusage.Record{
		Provider: "provider",
		Model:    "gpt-5.4",
		BaseURL:  "https://custom-proxy.example.com/v1",
	})
	if gotBaseURL != "https://custom-proxy.example.com/v1" {
		t.Fatalf("plugin BaseURL = %q, want https://custom-proxy.example.com/v1", gotBaseURL)
	}
}

func TestUsageAdapterPropagatesResponseModelServiceTierAndStream(t *testing.T) {
	var gotRecord pluginapi.UsageRecord
	plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		gotRecord = record
	})
	host := newHostWithRecords(capabilityRecord{
		id: "usage-response-fields",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: plugin,
		}},
	})
	adapter := &usageAdapter{
		host:     host,
		pluginID: "usage-response-fields",
	}

	adapter.HandleUsage(context.Background(), coreusage.Record{
		Provider:             "provider",
		Model:                "gpt-5.4",
		ResponseModel:        "gpt-5.6-luna",
		ServiceTier:          "auto",
		EffectiveServiceTier: "priority",
		ResponseServiceTier:  "default",
		Stream:               true,
	})
	if gotRecord.Model != "gpt-5.4" {
		t.Fatalf("plugin Model = %q, want gpt-5.4", gotRecord.Model)
	}
	if gotRecord.ResponseModel != "gpt-5.6-luna" {
		t.Fatalf("plugin ResponseModel = %q, want gpt-5.6-luna", gotRecord.ResponseModel)
	}
	if gotRecord.ServiceTier != "auto" {
		t.Fatalf("plugin ServiceTier = %q, want auto", gotRecord.ServiceTier)
	}
	if gotRecord.EffectiveServiceTier != "priority" {
		t.Fatalf("plugin EffectiveServiceTier = %q, want priority", gotRecord.EffectiveServiceTier)
	}
	if gotRecord.ResponseServiceTier != "default" {
		t.Fatalf("plugin ResponseServiceTier = %q, want default", gotRecord.ResponseServiceTier)
	}
	if !gotRecord.Stream {
		t.Fatalf("plugin Stream = %v, want true", gotRecord.Stream)
	}

	// Verify empty fields and Stream=false propagate cleanly without corruption.
	adapter.HandleUsage(context.Background(), coreusage.Record{
		Provider: "provider",
		Model:    "gpt-5.4",
		Stream:   false,
	})
	if gotRecord.Model != "gpt-5.4" {
		t.Fatalf("plugin Model = %q, want gpt-5.4", gotRecord.Model)
	}
	if gotRecord.ResponseModel != "" {
		t.Fatalf("plugin ResponseModel = %q, want empty", gotRecord.ResponseModel)
	}
	if gotRecord.EffectiveServiceTier != "" {
		t.Fatalf("plugin EffectiveServiceTier = %q, want empty", gotRecord.EffectiveServiceTier)
	}
	if gotRecord.ResponseServiceTier != "" {
		t.Fatalf("plugin ResponseServiceTier = %q, want empty", gotRecord.ResponseServiceTier)
	}
	if gotRecord.Stream {
		t.Fatalf("plugin Stream = %v, want false", gotRecord.Stream)
	}
}

func TestUsageAdapterPropagatesRequestIDAndTraceID(t *testing.T) {
	var gotRecord pluginapi.UsageRecord
	plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		gotRecord = record
	})
	host := newHostWithRecords(capabilityRecord{
		id: "usage-request-trace-id",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: plugin,
		}},
	})
	host.RegisterUsagePlugins()

	adapter := &usageAdapter{host: host, pluginID: "usage-request-trace-id", plugin: plugin}
	adapter.HandleUsage(context.Background(), coreusage.Record{
		RequestID: "b5db448b-3d6d-495c-9c71-f925b68926cb",
		TraceID:   "00000001",
		Provider:  "codex",
		Model:     "gpt-5.6-luna",
	})

	if gotRecord.RequestID != "b5db448b-3d6d-495c-9c71-f925b68926cb" {
		t.Fatalf("plugin RequestID = %q, want b5db448b-3d6d-495c-9c71-f925b68926cb", gotRecord.RequestID)
	}
	if gotRecord.TraceID != "00000001" {
		t.Fatalf("plugin TraceID = %q, want 00000001", gotRecord.TraceID)
	}
}

func TestUsageAdapterPropagatesModalityTokenDetails(t *testing.T) {
	var gotRecord pluginapi.UsageRecord
	plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		gotRecord = record
	})
	host := newHostWithRecords(capabilityRecord{
		id: "usage-modality-details",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: plugin,
		}},
	})
	adapter := &usageAdapter{
		host:     host,
		pluginID: "usage-modality-details",
		plugin:   plugin,
	}

	textIn := int64(30)
	imageIn := int64(50)
	audioIn := int64(0)
	videoIn := int64(18)
	fileIn := int64(12)
	cachedText := int64(25)
	cachedImage := int64(15)
	cachedAudio := int64(0)
	textOut := int64(0)
	imageOut := int64(1056)
	audioOut := int64(42)

	adapter.HandleUsage(context.Background(), coreusage.Record{
		Provider: "openai",
		Model:    "gpt-image-2.5-flare",
		Detail: coreusage.Detail{
			InputTokens:         110,
			OutputTokens:        1098,
			ReasoningTokens:     10,
			CachedTokens:        40,
			CacheReadTokens:     40,
			CacheCreationTokens: 5,
			TotalTokens:         1208,
			TextInputTokens:     &textIn,
			ImageInputTokens:    &imageIn,
			AudioInputTokens:    &audioIn,
			VideoInputTokens:    &videoIn,
			FileInputTokens:     &fileIn,
			CachedTextTokens:    &cachedText,
			CachedImageTokens:   &cachedImage,
			CachedAudioTokens:   &cachedAudio,
			TextOutputTokens:    &textOut,
			ImageOutputTokens:   &imageOut,
			AudioOutputTokens:   &audioOut,
		},
	})

	// Mutate caller-owned source values after HandleUsage to verify independent pointer copies.
	textIn = 999
	imageIn = 999
	audioIn = 999
	videoIn = 999
	fileIn = 999
	cachedText = 999
	cachedImage = 999
	cachedAudio = 999
	textOut = 999
	imageOut = 999
	audioOut = 999

	got := gotRecord.Detail
	if got.InputTokens != 110 || got.OutputTokens != 1098 || got.ReasoningTokens != 10 ||
		got.CachedTokens != 40 || got.CacheReadTokens != 40 || got.CacheCreationTokens != 5 || got.TotalTokens != 1208 {
		t.Fatalf("aggregate Detail = %+v", got)
	}
	assertInt64Ptr := func(name string, ptr *int64, src *int64, want int64) {
		t.Helper()
		if ptr == nil {
			t.Fatalf("%s = nil, want %d", name, want)
		}
		if ptr == src {
			t.Fatalf("%s shares pointer %p with source record", name, ptr)
		}
		if *ptr != want {
			t.Fatalf("%s = %d, want %d", name, *ptr, want)
		}
	}
	assertInt64Ptr("TextInputTokens", got.TextInputTokens, &textIn, 30)
	assertInt64Ptr("ImageInputTokens", got.ImageInputTokens, &imageIn, 50)
	assertInt64Ptr("AudioInputTokens", got.AudioInputTokens, &audioIn, 0)
	assertInt64Ptr("VideoInputTokens", got.VideoInputTokens, &videoIn, 18)
	assertInt64Ptr("FileInputTokens", got.FileInputTokens, &fileIn, 12)
	assertInt64Ptr("CachedTextTokens", got.CachedTextTokens, &cachedText, 25)
	assertInt64Ptr("CachedImageTokens", got.CachedImageTokens, &cachedImage, 15)
	assertInt64Ptr("CachedAudioTokens", got.CachedAudioTokens, &cachedAudio, 0)
	assertInt64Ptr("TextOutputTokens", got.TextOutputTokens, &textOut, 0)
	assertInt64Ptr("ImageOutputTokens", got.ImageOutputTokens, &imageOut, 1056)
	assertInt64Ptr("AudioOutputTokens", got.AudioOutputTokens, &audioOut, 42)

	// Verify absent counters remain nil and distinct from explicit zero.
	adapter.HandleUsage(context.Background(), coreusage.Record{
		Provider: "openai",
		Model:    "gpt-image-2.5-flare",
		Detail: coreusage.Detail{
			InputTokens:      80,
			TextInputTokens:  coreusage.Int64Ptr(30),
			ImageInputTokens: coreusage.Int64Ptr(50),
			TextOutputTokens: coreusage.Int64Ptr(0),
		},
	})
	got = gotRecord.Detail
	if got.TextInputTokens == nil || *got.TextInputTokens != 30 {
		t.Fatalf("TextInputTokens = %v, want 30", got.TextInputTokens)
	}
	if got.ImageInputTokens == nil || *got.ImageInputTokens != 50 {
		t.Fatalf("ImageInputTokens = %v, want 50", got.ImageInputTokens)
	}
	if got.TextOutputTokens == nil || *got.TextOutputTokens != 0 {
		t.Fatalf("TextOutputTokens = %v, want explicit 0", got.TextOutputTokens)
	}
	if got.AudioInputTokens != nil || got.VideoInputTokens != nil || got.FileInputTokens != nil ||
		got.CachedTextTokens != nil || got.CachedImageTokens != nil || got.CachedAudioTokens != nil ||
		got.ImageOutputTokens != nil || got.AudioOutputTokens != nil {
		t.Fatalf("expected absent modality counters to remain nil, got %+v", got)
	}
}

func TestUsageAdapterPreservesExplicitGenerateFalse(t *testing.T) {
	var gotGenerate bool
	plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		gotGenerate = record.Generate
	})
	host := newHostWithRecords(capabilityRecord{
		id: "usage-generate-false",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: plugin,
		}},
	})
	adapter := &usageAdapter{
		host:     host,
		pluginID: "usage-generate-false",
	}

	adapter.HandleUsage(context.Background(), coreusage.Record{
		Provider: "provider",
		Model:    "gpt-5.4",
		Generate: coreusage.GenerateFlag(false),
	})
	if gotGenerate {
		t.Fatalf("plugin Generate = %v, want false", gotGenerate)
	}
}

func TestUsageAdapterDetachesContext(t *testing.T) {
	var receivedCtx context.Context
	plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		receivedCtx = ctx
	})
	host := newHostWithRecords(capabilityRecord{
		id: "usage-detach",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: plugin,
		}},
	})
	adapter := &usageAdapter{
		host:     host,
		pluginID: "usage-detach",
	}

	canceledCtx, cancel := context.WithCancel(context.Background())
	cancel()

	adapter.HandleUsage(canceledCtx, coreusage.Record{
		Provider: "provider",
		Model:    "gpt-5.4",
	})
	if receivedCtx == nil {
		t.Fatal("plugin did not receive context")
	}
	if errCtx := receivedCtx.Err(); errCtx != nil {
		t.Fatalf("expected detached context without error, got ctx.Err() = %v", errCtx)
	}

	receivedCtx = nil
	adapter.HandleUsage(nil, coreusage.Record{
		Provider: "provider",
		Model:    "gpt-5.4",
	})
	if receivedCtx == nil {
		t.Fatal("plugin did not receive context for nil input")
	}
	if errCtx := receivedCtx.Err(); errCtx != nil {
		t.Fatalf("expected non-nil context without error for nil input, got ctx.Err() = %v", errCtx)
	}
}

func TestUsageManagerRegisterNamedReplacesWithoutDuplicateDispatch(t *testing.T) {
	manager := coreusage.NewManager(0)
	defer manager.Stop()

	calls := make(chan string, 2)
	manager.RegisterNamed("plugin:alpha", coreUsagePluginFunc(func(ctx context.Context, record coreusage.Record) {
		calls <- "first"
	}))
	manager.RegisterNamed("plugin:alpha", coreUsagePluginFunc(func(ctx context.Context, record coreusage.Record) {
		calls <- "second"
	}))

	manager.Publish(context.Background(), coreusage.Record{Provider: "provider"})

	select {
	case got := <-calls:
		if got != "second" {
			t.Fatalf("first dispatch = %q, want second", got)
		}
	case <-time.After(100 * time.Millisecond):
		t.Fatal("timed out waiting for usage dispatch")
	}
	select {
	case got := <-calls:
		t.Fatalf("unexpected duplicate dispatch from %q", got)
	case <-time.After(50 * time.Millisecond):
	}
}

func TestRegisterFrontendAuthProvidersPrunesStaleKeys(t *testing.T) {
	const key = "plugin:auth-active:custom-auth"
	sdkaccess.UnregisterProvider(key)
	defer sdkaccess.UnregisterProvider(key)

	host := newHostWithRecords(capabilityRecord{
		id: "auth-active",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			FrontendAuthProvider: frontendAuthProviderFunc{
				identifier: "custom-auth",
				authenticate: func(ctx context.Context, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
					return pluginapi.FrontendAuthResponse{Authenticated: true}, nil
				},
			},
		}},
	})

	host.RegisterFrontendAuthProviders()
	if !registeredProviderIdentifier(key) {
		t.Fatalf("registered providers did not include %q", key)
	}

	setHostSnapshotForTest(host, true)
	host.RegisterFrontendAuthProviders()
	if registeredProviderIdentifier(key) {
		t.Fatalf("registered providers still included stale key %q", key)
	}
}

func TestRegisterFrontendAuthProvidersIdentifierPanicFusesPlugin(t *testing.T) {
	host := newHostWithRecords(capabilityRecord{
		id: "auth-identifier-panic",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			FrontendAuthProvider: panicFrontendAuthProvider{},
		}},
	})

	host.RegisterFrontendAuthProviders()

	if !host.isPluginFused("auth-identifier-panic") {
		t.Fatal("auth-identifier-panic was not fused")
	}
}

func TestRegisterFrontendAuthProvidersSelectsHighestPriorityExclusiveProvider(t *testing.T) {
	lowKey := "plugin:exclusive-low:custom-auth"
	highKey := "plugin:exclusive-high:custom-auth"
	normalKey := "plugin:normal-auth:custom-auth"
	for _, key := range []string{lowKey, highKey, normalKey} {
		sdkaccess.UnregisterProvider(key)
		defer sdkaccess.UnregisterProvider(key)
	}
	sdkaccess.ClearExclusiveProvider()
	defer sdkaccess.ClearExclusiveProvider()

	host := newHostWithRecords(
		capabilityRecord{
			id:       "exclusive-low",
			priority: 1,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				FrontendAuthProvider:          frontendAuthProviderFunc{identifier: "custom-auth"},
				FrontendAuthProviderExclusive: true,
			}},
		},
		capabilityRecord{
			id:       "exclusive-high",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				FrontendAuthProvider:          frontendAuthProviderFunc{identifier: "custom-auth"},
				FrontendAuthProviderExclusive: true,
			}},
		},
		capabilityRecord{
			id:       "normal-auth",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				FrontendAuthProvider: frontendAuthProviderFunc{identifier: "custom-auth"},
			}},
		},
	)

	host.RegisterFrontendAuthProviders()

	providers := sdkaccess.RegisteredProviders()
	if len(providers) != 1 {
		t.Fatalf("RegisteredProviders() len = %d, want 1", len(providers))
	}
	if providers[0].Identifier() != highKey {
		t.Fatalf("exclusive provider = %q, want %q", providers[0].Identifier(), highKey)
	}
}

func TestRegisterFrontendAuthProvidersSelectsExclusiveProviderByPluginIDWhenPriorityTies(t *testing.T) {
	alphaKey := "plugin:alpha-auth:custom-auth"
	betaKey := "plugin:beta-auth:custom-auth"
	for _, key := range []string{alphaKey, betaKey} {
		sdkaccess.UnregisterProvider(key)
		defer sdkaccess.UnregisterProvider(key)
	}
	sdkaccess.ClearExclusiveProvider()
	defer sdkaccess.ClearExclusiveProvider()

	host := newHostWithRecords(
		capabilityRecord{
			id:       "beta-auth",
			priority: 5,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				FrontendAuthProvider:          frontendAuthProviderFunc{identifier: "custom-auth"},
				FrontendAuthProviderExclusive: true,
			}},
		},
		capabilityRecord{
			id:       "alpha-auth",
			priority: 5,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				FrontendAuthProvider:          frontendAuthProviderFunc{identifier: "custom-auth"},
				FrontendAuthProviderExclusive: true,
			}},
		},
	)

	host.RegisterFrontendAuthProviders()

	providers := sdkaccess.RegisteredProviders()
	if len(providers) != 1 {
		t.Fatalf("RegisteredProviders() len = %d, want 1", len(providers))
	}
	if providers[0].Identifier() != alphaKey {
		t.Fatalf("exclusive provider = %q, want %q", providers[0].Identifier(), alphaKey)
	}
}

func TestRegisterFrontendAuthProvidersClearsExclusiveProviderWhenExclusivePluginRemoved(t *testing.T) {
	exclusiveKey := "plugin:exclusive-auth:custom-auth"
	normalKey := "plugin:normal-auth:custom-auth"
	for _, key := range []string{exclusiveKey, normalKey} {
		sdkaccess.UnregisterProvider(key)
		defer sdkaccess.UnregisterProvider(key)
	}
	sdkaccess.ClearExclusiveProvider()
	defer sdkaccess.ClearExclusiveProvider()

	host := newHostWithRecords(
		capabilityRecord{
			id: "exclusive-auth",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				FrontendAuthProvider:          frontendAuthProviderFunc{identifier: "custom-auth"},
				FrontendAuthProviderExclusive: true,
			}},
		},
		capabilityRecord{
			id: "normal-auth",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				FrontendAuthProvider: frontendAuthProviderFunc{identifier: "custom-auth"},
			}},
		},
	)

	host.RegisterFrontendAuthProviders()
	if got := sdkaccess.RegisteredProviders(); len(got) != 1 || got[0].Identifier() != exclusiveKey {
		t.Fatalf("exclusive RegisteredProviders() = %#v, want only %q", got, exclusiveKey)
	}

	setHostSnapshotForTest(host, true, capabilityRecord{
		id: "normal-auth",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			FrontendAuthProvider: frontendAuthProviderFunc{identifier: "custom-auth"},
		}},
	})
	host.RegisterFrontendAuthProviders()

	providers := sdkaccess.RegisteredProviders()
	if len(providers) != 1 {
		t.Fatalf("RegisteredProviders() len = %d, want 1", len(providers))
	}
	if providers[0].Identifier() != normalKey {
		t.Fatalf("restored provider = %q, want %q", providers[0].Identifier(), normalKey)
	}
}

func TestRegisterFrontendAuthProvidersIgnoresExclusiveWithoutFrontendAuthProvider(t *testing.T) {
	normalKey := "plugin:normal-auth:custom-auth"
	sdkaccess.UnregisterProvider(normalKey)
	sdkaccess.ClearExclusiveProvider()
	defer sdkaccess.UnregisterProvider(normalKey)
	defer sdkaccess.ClearExclusiveProvider()

	host := newHostWithRecords(
		capabilityRecord{
			id: "exclusive-without-provider",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				FrontendAuthProviderExclusive: true,
			}},
		},
		capabilityRecord{
			id: "normal-auth",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				FrontendAuthProvider: frontendAuthProviderFunc{identifier: "custom-auth"},
			}},
		},
	)

	host.RegisterFrontendAuthProviders()

	providers := sdkaccess.RegisteredProviders()
	if len(providers) != 1 {
		t.Fatalf("RegisteredProviders() len = %d, want 1", len(providers))
	}
	if providers[0].Identifier() != normalKey {
		t.Fatalf("provider = %q, want %q", providers[0].Identifier(), normalKey)
	}
}

func TestUsageAdapterUsesCurrentSnapshotCapability(t *testing.T) {
	oldCalls := 0
	newCalls := 0
	oldPlugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		oldCalls++
	})
	newPlugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		newCalls++
	})
	host := newHostWithRecords(capabilityRecord{
		id: "usage-active",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: oldPlugin,
		}},
	})
	adapter := &usageAdapter{
		host:     host,
		pluginID: "usage-active",
		plugin:   oldPlugin,
	}
	setHostSnapshotForTest(host, true, capabilityRecord{
		id: "usage-active",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: newPlugin,
		}},
	})

	adapter.HandleUsage(context.Background(), coreusage.Record{Provider: "provider"})

	if oldCalls != 0 {
		t.Fatalf("old usage plugin calls = %d, want 0", oldCalls)
	}
	if newCalls != 1 {
		t.Fatalf("new usage plugin calls = %d, want 1", newCalls)
	}
}

func TestRegisterUsagePluginsStaleAdapterSkipsRemovedCapability(t *testing.T) {
	calls := 0
	plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
		calls++
	})
	host := newHostWithRecords(capabilityRecord{
		id: "usage-active",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			UsagePlugin: plugin,
		}},
	})

	host.RegisterUsagePlugins()
	adapter := &usageAdapter{
		host:     host,
		pluginID: "usage-active",
		plugin:   plugin,
	}
	setHostSnapshotForTest(host, true)
	adapter.HandleUsage(context.Background(), coreusage.Record{Provider: "provider"})

	if calls != 0 {
		t.Fatalf("usage plugin calls = %d, want 0 after capability removal", calls)
	}
}

func TestAccessAdapterAuthenticateFailures(t *testing.T) {
	tests := []struct {
		name             string
		pluginID         string
		method           string
		url              string
		body             io.ReadCloser
		authenticate     func(*testing.T, pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error)
		wantCode         sdkaccess.AuthErrorCode
		wantCalled       bool
		wantFused        bool
		wantRestoredBody string
	}{
		{
			name:     "unauthenticated",
			pluginID: "auth-plugin",
			method:   http.MethodGet,
			url:      "http://example.test/v1/models",
			authenticate: func(t *testing.T, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
				return pluginapi.FrontendAuthResponse{Authenticated: false}, nil
			},
			wantCode:   sdkaccess.AuthErrorCodeNotHandled,
			wantCalled: true,
		},
		{
			name:     "denied with status and message",
			pluginID: "auth-plugin",
			method:   http.MethodPost,
			url:      "http://example.test/v1/responses",
			authenticate: func(t *testing.T, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
				return pluginapi.FrontendAuthResponse{
					Authenticated:  false,
					DenyStatusCode: http.StatusTooManyRequests,
					DenyMessage:    "quota exhausted",
				}, nil
			},
			wantCode:   sdkaccess.AuthErrorCodeDenied,
			wantCalled: true,
		},
		{
			name:     "panic",
			pluginID: "auth-panic",
			method:   http.MethodGet,
			url:      "http://example.test/v1/models",
			authenticate: func(t *testing.T, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
				panic("auth panic")
			},
			wantCode:   sdkaccess.AuthErrorCodeNotHandled,
			wantCalled: true,
			wantFused:  true,
		},
		{
			name:     "body read failure",
			pluginID: "auth-plugin",
			method:   http.MethodPost,
			url:      "http://example.test/v1/chat",
			body:     failingReadCloser{},
			authenticate: func(t *testing.T, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
				return pluginapi.FrontendAuthResponse{Authenticated: true}, nil
			},
			wantCode: sdkaccess.AuthErrorCodeInternal,
		},
		{
			name:     "provider error restores body",
			pluginID: "auth-plugin",
			method:   http.MethodPost,
			url:      "http://example.test/v1/chat?x=1",
			body:     io.NopCloser(bytes.NewBufferString("request-body")),
			authenticate: func(t *testing.T, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
				if string(req.Body) != "request-body" {
					t.Fatalf("plugin request body = %q, want %q", req.Body, "request-body")
				}
				return pluginapi.FrontendAuthResponse{}, fmt.Errorf("not mine")
			},
			wantCode:         sdkaccess.AuthErrorCodeNotHandled,
			wantCalled:       true,
			wantRestoredBody: "request-body",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			host := New()
			called := false
			adapter := newAccessAdapterForTest(host, tt.pluginID, frontendAuthProviderFunc{
				identifier: "custom-auth",
				authenticate: func(ctx context.Context, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
					called = true
					return tt.authenticate(t, req)
				},
			})
			req, errNewRequest := http.NewRequest(tt.method, tt.url, tt.body)
			if errNewRequest != nil {
				t.Fatalf("NewRequest() error = %v", errNewRequest)
			}

			result, authErr := adapter.Authenticate(context.Background(), req)
			if result != nil {
				t.Fatalf("Authenticate() result = %#v, want nil", result)
			}
			if !sdkaccess.IsAuthErrorCode(authErr, tt.wantCode) {
				t.Fatalf("Authenticate() error = %v, want code %s", authErr, tt.wantCode)
			}
			if called != tt.wantCalled {
				t.Fatalf("provider called = %v, want %v", called, tt.wantCalled)
			}
			if tt.wantFused && !host.isPluginFused(tt.pluginID) {
				t.Fatalf("%s was not fused", tt.pluginID)
			}
			if tt.wantRestoredBody != "" {
				restored, errReadAll := io.ReadAll(req.Body)
				if errReadAll != nil {
					t.Fatalf("ReadAll(restored body) error = %v", errReadAll)
				}
				if string(restored) != tt.wantRestoredBody {
					t.Fatalf("restored body = %q, want %q", restored, tt.wantRestoredBody)
				}
			}
		})
	}
}

func TestAccessAdapterAuthenticateMaxBytesErrorReturns413(t *testing.T) {
	host := New()
	called := false
	adapter := newAccessAdapterForTest(host, "auth-plugin", frontendAuthProviderFunc{
		identifier: "custom-auth",
		authenticate: func(ctx context.Context, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
			called = true
			return pluginapi.FrontendAuthResponse{Authenticated: true}, nil
		},
	})
	limitedBody := http.MaxBytesReader(nil, io.NopCloser(strings.NewReader("0123456789")), 4)
	req, errNewRequest := http.NewRequest(http.MethodPost, "http://example.test/v1/chat", limitedBody)
	if errNewRequest != nil {
		t.Fatalf("NewRequest() error = %v", errNewRequest)
	}

	result, authErr := adapter.Authenticate(context.Background(), req)
	if result != nil {
		t.Fatalf("Authenticate() result = %#v, want nil", result)
	}
	if authErr == nil {
		t.Fatal("Authenticate() error = nil, want 413 auth error")
	}
	if got := authErr.HTTPStatusCode(); got != http.StatusRequestEntityTooLarge {
		t.Fatalf("Authenticate() status = %d, want %d (err=%v)", got, http.StatusRequestEntityTooLarge, authErr)
	}
	if called {
		t.Fatal("provider Authenticate() was called despite oversized request body")
	}
}

func TestExecutorAdapterMethods(t *testing.T) {
	streamChunks := make(chan pluginapi.ExecutorStreamChunk, 2)
	streamErr := errors.New("stream failed")
	streamChunks <- pluginapi.ExecutorStreamChunk{Payload: []byte("stream-1")}
	streamChunks <- pluginapi.ExecutorStreamChunk{Err: streamErr}
	close(streamChunks)

	pluginHTTPBody := []byte("http-response")
	pluginHTTPHeaders := http.Header{"X-Http": []string{"1"}}
	authProvider := fakeAuthProvider{
		identifier: "plugin-provider",
		refreshAuth: func(ctx context.Context, req pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
			if req.AuthID != "auth-1" || req.AuthProvider != "plugin-provider" || req.Metadata["old"] != "value" {
				t.Fatalf("refresh request = %#v, want auth metadata", req)
			}
			if req.HTTPClient == nil {
				t.Fatal("refresh request HTTPClient = nil, want host HTTP bridge")
			}
			return pluginapi.AuthRefreshResponse{
				Auth: pluginapi.AuthData{
					Metadata:   map[string]any{"token": "new", "priority": float64(0)},
					Attributes: map[string]string{"priority": "0"},
				},
			}, nil
		},
	}
	executorRecord := normalizeTestCapabilityRecord(capabilityRecord{id: "executor-plugin"})
	host := newHostWithRecords(
		capabilityRecord{
			id: "auth-plugin",
			plugin: pluginapi.Plugin{
				Capabilities: pluginapi.Capabilities{
					AuthProvider: authProvider,
				},
			},
		},
		executorRecord,
	)

	exec := &fakeExecutor{
		identifier: "ignored-by-adapter",
		execute: func(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error) {
			assertExecutorRequest(t, req)
			return pluginapi.ExecutorResponse{
				Payload: []byte("execute-response"),
				Headers: http.Header{"X-Execute": []string{"1"}},
				Metadata: map[string]any{
					"phase": "execute",
				},
			}, nil
		},
		executeStream: func(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorStreamResponse, error) {
			assertExecutorRequest(t, req)
			return pluginapi.ExecutorStreamResponse{
				Headers: http.Header{"X-Stream": []string{"1"}},
				Chunks:  streamChunks,
			}, nil
		},
		countTokens: func(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error) {
			assertExecutorRequest(t, req)
			return pluginapi.ExecutorResponse{Payload: []byte(`{"total_tokens":3}`)}, nil
		},
		httpRequest: func(ctx context.Context, req pluginapi.ExecutorHTTPRequest) (pluginapi.ExecutorHTTPResponse, error) {
			if req.AuthID != "auth-1" || req.AuthProvider != "plugin-provider" || req.Method != http.MethodPatch ||
				req.URL != "http://example.test/v1/raw?x=1" || req.Headers.Get("X-Raw") != "yes" || string(req.Body) != "raw-body" {
				t.Fatalf("http request = %#v, want mapped raw HTTP request", req)
			}
			if req.HTTPClient == nil {
				t.Fatal("http request HTTPClient = nil, want host HTTP bridge")
			}
			return pluginapi.ExecutorHTTPResponse{
				StatusCode: http.StatusAccepted,
				Headers:    pluginHTTPHeaders,
				Body:       pluginHTTPBody,
			}, nil
		},
	}
	adapter := newExecutorAdapterForRecordForTest(host, executorRecord, exec,
		[]sdktranslator.Format{sdktranslator.FormatOpenAI},
		[]sdktranslator.Format{sdktranslator.FormatOpenAI},
	)
	auth := &coreauth.Auth{
		ID:       "auth-1",
		Provider: "plugin-provider",
		Metadata: map[string]any{"old": "value", "priority": float64(1)},
		Attributes: map[string]string{
			coreauth.AttributeSourceBackend: coreauth.AuthSourceFile,
			coreauth.AttributeFilePriority:  "true",
			"priority":                      "1",
		},
	}
	req := coreexecutor.Request{
		Model:   "model-1",
		Format:  sdktranslator.FormatOpenAI,
		Payload: []byte("payload"),
		Metadata: map[string]any{
			"req": "metadata",
		},
	}
	opts := coreexecutor.Options{
		Stream:          true,
		Alt:             "alt",
		Headers:         http.Header{"X-Request": []string{"yes"}},
		OriginalRequest: []byte("original"),
		SourceFormat:    sdktranslator.FormatOpenAI,
		Metadata: map[string]any{
			"opt": "metadata",
		},
	}

	if adapter.Identifier() != "plugin-provider" {
		t.Fatalf("Identifier() = %q, want %q", adapter.Identifier(), "plugin-provider")
	}
	resp, errExecute := adapter.Execute(context.Background(), auth, req, opts)
	if errExecute != nil {
		t.Fatalf("Execute() error = %v", errExecute)
	}
	if string(resp.Payload) != "execute-response" || resp.Headers.Get("X-Execute") != "1" || resp.Metadata["phase"] != "execute" {
		t.Fatalf("Execute() = %#v, want mapped response", resp)
	}

	stream, errExecuteStream := adapter.ExecuteStream(context.Background(), auth, req, opts)
	if errExecuteStream != nil {
		t.Fatalf("ExecuteStream() error = %v", errExecuteStream)
	}
	if stream.Headers.Get("X-Stream") != "1" {
		t.Fatalf("ExecuteStream() headers = %#v, want X-Stream", stream.Headers)
	}
	first := <-stream.Chunks
	if string(first.Payload) != "stream-1" || first.Err != nil {
		t.Fatalf("first stream chunk = %#v, want payload chunk", first)
	}
	second := <-stream.Chunks
	if second.Err != streamErr {
		t.Fatalf("second stream chunk err = %v, want %v", second.Err, streamErr)
	}
	if _, ok := <-stream.Chunks; ok {
		t.Fatal("stream chunks channel still open, want closed")
	}

	refreshed, errRefresh := adapter.Refresh(context.Background(), auth)
	if errRefresh != nil {
		t.Fatalf("Refresh() error = %v", errRefresh)
	}
	if refreshed == auth {
		t.Fatal("Refresh() returned original auth pointer, want clone")
	}
	if refreshed.Metadata["token"] != "new" {
		t.Fatalf("Refresh() metadata = %#v, want token=new", refreshed.Metadata)
	}
	if refreshed.Attributes["priority"] != "1" || refreshed.Metadata["priority"] != float64(1) {
		t.Fatalf("Refresh() priority = %q/%v, want 1/1", refreshed.Attributes["priority"], refreshed.Metadata["priority"])
	}

	count, errCountTokens := adapter.CountTokens(context.Background(), auth, req, opts)
	if errCountTokens != nil {
		t.Fatalf("CountTokens() error = %v", errCountTokens)
	}
	if string(count.Payload) != `{"total_tokens":3}` {
		t.Fatalf("CountTokens() payload = %q, want token payload", count.Payload)
	}

	rawReq, errNewRawRequest := http.NewRequest(http.MethodPatch, "http://example.test/v1/raw?x=1", bytes.NewBufferString("raw-body"))
	if errNewRawRequest != nil {
		t.Fatalf("NewRequest(raw) error = %v", errNewRawRequest)
	}
	rawReq.Header.Set("X-Raw", "yes")
	httpResp, errHTTPRequest := adapter.HttpRequest(context.Background(), auth, rawReq)
	if errHTTPRequest != nil {
		t.Fatalf("HttpRequest() error = %v", errHTTPRequest)
	}
	if httpResp.StatusCode != http.StatusAccepted || httpResp.Status != "202 Accepted" || httpResp.Header.Get("X-Http") != "1" {
		t.Fatalf("HttpRequest() response = %#v, want mapped status/header", httpResp)
	}
	pluginHTTPBody[0] = 'X'
	pluginHTTPHeaders.Set("X-Http", "mutated")
	body, errReadBody := io.ReadAll(httpResp.Body)
	if errReadBody != nil {
		t.Fatalf("ReadAll(HttpRequest body) error = %v", errReadBody)
	}
	if string(body) != "http-response" || httpResp.Header.Get("X-Http") != "1" {
		t.Fatalf("HttpRequest() response aliases plugin data: body=%q header=%q", body, httpResp.Header.Get("X-Http"))
	}
	restoredRawBody, errReadRawBody := io.ReadAll(rawReq.Body)
	if errReadRawBody != nil {
		t.Fatalf("ReadAll(restored raw request body) error = %v", errReadRawBody)
	}
	if string(restoredRawBody) != "raw-body" {
		t.Fatalf("restored raw request body = %q, want raw-body", restoredRawBody)
	}

	nilResp, errNilRequest := adapter.HttpRequest(context.Background(), auth, nil)
	if nilResp != nil {
		t.Fatalf("HttpRequest(nil) response = %#v, want nil", nilResp)
	}
	if errNilRequest == nil || !strings.Contains(errNilRequest.Error(), "nil HTTP request") {
		t.Fatalf("HttpRequest(nil) error = %v, want nil request error", errNilRequest)
	}
}

func TestExecutorAdapterUsesResponseFormatForOutputTranslation(t *testing.T) {
	claudeResponse := []byte(`{"id":"msg_1","type":"message","model":"claude-test","role":"assistant","content":[{"type":"text","text":"ok"}],"usage":{"input_tokens":1,"output_tokens":1}}`)
	openAIRequest := []byte(`{"model":"model-1","messages":[{"role":"user","content":"hi"}]}`)

	var captured pluginapi.ExecutorRequest
	host := New()
	adapter := newCurrentExecutorAdapterForTest(host, "executor-plugin", &fakeExecutor{
		execute: func(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error) {
			captured = req
			return pluginapi.ExecutorResponse{Payload: claudeResponse}, nil
		},
	},
		[]sdktranslator.Format{sdktranslator.FormatClaude},
		[]sdktranslator.Format{sdktranslator.FormatClaude},
	)

	resp, errExecute := adapter.Execute(context.Background(), &coreauth.Auth{}, coreexecutor.Request{
		Model:   "model-1",
		Format:  sdktranslator.FormatOpenAI,
		Payload: openAIRequest,
	}, coreexecutor.Options{
		SourceFormat:   sdktranslator.FormatOpenAI,
		ResponseFormat: sdktranslator.FormatClaude,
	})
	if errExecute != nil {
		t.Fatalf("Execute() error = %v", errExecute)
	}
	if captured.SourceFormat != sdktranslator.FormatClaude.String() {
		t.Fatalf("executor SourceFormat = %q, want %q", captured.SourceFormat, sdktranslator.FormatClaude)
	}
	if captured.Format != sdktranslator.FormatClaude.String() {
		t.Fatalf("executor Format = %q, want %q", captured.Format, sdktranslator.FormatClaude)
	}
	if bytes.Equal(captured.Payload, openAIRequest) || !bytes.Contains(captured.Payload, []byte(`"max_tokens":32000`)) {
		t.Fatalf("executor payload = %s, want translated Claude request", captured.Payload)
	}
	if !bytes.Equal(resp.Payload, claudeResponse) {
		t.Fatalf("Execute() payload = %s, want Claude response payload %s", resp.Payload, claudeResponse)
	}
}

func TestExecutorAdapterSelectsCustomOutputWithHostResponseTranslator(t *testing.T) {
	customOutputFormat := sdktranslator.Format("plugin-custom-output")
	requestedFormat := sdktranslator.FormatOpenAI
	body := []byte("plugin-body")
	translatedBody := []byte("translated-body")
	var captured pluginapi.ResponseTransformRequest

	executorRecord := normalizeTestCapabilityRecord(capabilityRecord{id: "executor-plugin"})
	host := newHostWithRecords(
		capabilityRecord{
			id: "response-translator",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ResponseTranslator: responseTranslatorFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
					captured = req
					return pluginapi.PayloadResponse{Body: translatedBody}, nil
				}),
			}},
		},
		executorRecord,
	)
	sdktranslator.SetPluginHooks(host)
	t.Cleanup(func() {
		sdktranslator.SetPluginHooks(nil)
	})

	adapter := newExecutorAdapterForRecordForTest(host, executorRecord, &fakeExecutor{
		execute: func(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error) {
			if req.Format != customOutputFormat.String() {
				t.Fatalf("executor Format = %q, want %q", req.Format, customOutputFormat)
			}
			return pluginapi.ExecutorResponse{Payload: body}, nil
		},
	},
		[]sdktranslator.Format{sdktranslator.FormatOpenAI},
		[]sdktranslator.Format{customOutputFormat},
	)

	resp, errExecute := adapter.Execute(context.Background(), &coreauth.Auth{}, coreexecutor.Request{
		Model:   "model-1",
		Format:  sdktranslator.FormatOpenAI,
		Payload: []byte(`{"model":"model-1"}`),
	}, coreexecutor.Options{
		SourceFormat:   sdktranslator.FormatOpenAI,
		ResponseFormat: requestedFormat,
	})
	if errExecute != nil {
		t.Fatalf("Execute() error = %v", errExecute)
	}
	if !bytes.Equal(resp.Payload, translatedBody) {
		t.Fatalf("Execute() payload = %q, want %q", resp.Payload, translatedBody)
	}
	if captured.FromFormat != customOutputFormat.String() || captured.ToFormat != requestedFormat.String() {
		t.Fatalf("translator formats = %q -> %q, want %q -> %q", captured.FromFormat, captured.ToFormat, customOutputFormat, requestedFormat)
	}
	if captured.Stream {
		t.Fatal("translator Stream = true, want false")
	}
	if !bytes.Equal(captured.Body, body) {
		t.Fatalf("translator body = %q, want %q", captured.Body, body)
	}
}

func TestExecutorAdapterConsumesTranslatedStreamChunksWithoutOutput(t *testing.T) {
	adapter := &executorAdapter{}
	request := []byte(`{"model":"qmodel_latest","stream":true,"tool_choice":"auto","parallel_tool_calls":true}`)
	prepared := preparedExecutorCall{
		req: coreexecutor.Request{
			Model:   "qmodel_latest",
			Payload: request,
		},
		opts: coreexecutor.Options{
			OriginalRequest: request,
		},
		requestedFormat: sdktranslator.FormatOpenAIResponse,
		outputFormat:    sdktranslator.FormatOpenAI,
	}
	var param any

	startPayload := []byte(`{"choices":[{"delta":{"content":"","tool_calls":[{"function":{"arguments":"","name":"get_weather"},"id":"call_69755759d70640e3b7a42805","index":0,"type":"function"}]},"index":0}],"created":1780767281,"id":"chatcmpl-ba492ed2-2901-9d1f-80e7-b6dfe97fefaa","model":"auto","object":"chat.completion.chunk"}`)
	if got := adapter.translateExecutorStreamPayload(context.Background(), prepared, startPayload, &param); len(got) == 0 {
		t.Fatal("tool call start payload was not translated")
	}

	emptyArgumentsPayload := []byte(`{"choices":[{"delta":{"content":"","tool_calls":[{"function":{"arguments":""},"id":"","index":0,"type":"function"}]},"index":0}],"created":1780767281,"id":"chatcmpl-ba492ed2-2901-9d1f-80e7-b6dfe97fefaa","model":"auto","object":"chat.completion.chunk"}`)
	if got := adapter.translateExecutorStreamPayload(context.Background(), prepared, emptyArgumentsPayload, &param); len(got) != 0 {
		t.Fatalf("empty arguments payload leaked through translation fallback: %q", got[0])
	}

	finishPayload := []byte(`{"choices":[{"delta":{},"finish_reason":"tool_calls","index":0}],"created":1780767281,"id":"chatcmpl-ba492ed2-2901-9d1f-80e7-b6dfe97fefaa","model":"auto","object":"chat.completion.chunk"}`)
	if got := adapter.translateExecutorStreamPayload(context.Background(), prepared, finishPayload, &param); len(got) == 0 {
		t.Fatal("finish payload was not translated")
	}

	usagePayload := []byte(`{"choices":[],"created":1780767281,"id":"chatcmpl-ba492ed2-2901-9d1f-80e7-b6dfe97fefaa","model":"auto","object":"chat.completion.chunk","usage":{"completion_tokens":179,"completion_tokens_details":{"reasoning_tokens":121},"prompt_tokens":331,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":510}}`)
	if got := adapter.translateExecutorStreamPayload(context.Background(), prepared, usagePayload, &param); len(got) != 0 {
		t.Fatalf("usage-only payload leaked through translation fallback: %q", got[0])
	}

	donePayload := []byte(`data: [DONE]`)
	doneFrames := adapter.translateExecutorStreamPayload(context.Background(), prepared, donePayload, &param)
	if len(doneFrames) != 1 {
		t.Fatalf("done payload translated to %d frames, want 1", len(doneFrames))
	}
	if !bytes.Contains(doneFrames[0], []byte("response.completed")) {
		t.Fatalf("done payload did not produce response.completed: %q", doneFrames[0])
	}
	if !bytes.Contains(doneFrames[0], []byte(`"input_tokens":331`)) ||
		!bytes.Contains(doneFrames[0], []byte(`"output_tokens":179`)) ||
		!bytes.Contains(doneFrames[0], []byte(`"reasoning_tokens":121`)) ||
		!bytes.Contains(doneFrames[0], []byte(`"total_tokens":510`)) {
		t.Fatalf("completed payload did not preserve usage: %q", doneFrames[0])
	}
}

func TestExecutorAdapterKeepsRawStreamFallbackWithOnlyHostResponseTranslator(t *testing.T) {
	customOutputFormat := sdktranslator.Format("plugin-custom-stream-output")
	requestedFormat := sdktranslator.FormatOpenAI
	payload := []byte(`{"custom":"chunk"}`)
	host := newHostWithRecords(capabilityRecord{
		id: "empty-response-translator",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			ResponseTranslator: responseTranslatorFunc(func(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
				return pluginapi.PayloadResponse{}, nil
			}),
		}},
	})
	sdktranslator.SetPluginHooks(host)
	t.Cleanup(func() {
		sdktranslator.SetPluginHooks(nil)
	})
	adapter := &executorAdapter{
		host: host,
	}
	prepared := preparedExecutorCall{
		req: coreexecutor.Request{
			Model:   "model-1",
			Payload: []byte(`{"model":"model-1"}`),
		},
		opts: coreexecutor.Options{
			OriginalRequest: []byte(`{"model":"model-1","stream":true}`),
		},
		requestedFormat: requestedFormat,
		outputFormat:    customOutputFormat,
	}
	var param any

	frames := adapter.translateExecutorStreamPayload(context.Background(), prepared, payload, &param)
	if len(frames) != 1 {
		t.Fatalf("translated stream frame count = %d, want 1", len(frames))
	}
	if !bytes.Equal(frames[0], payload) {
		t.Fatalf("translated stream frame = %q, want raw payload %q", frames[0], payload)
	}
}

func TestExecutorAdapterPanicFusesAndReturnsError(t *testing.T) {
	host := New()
	calls := 0
	adapter := newCurrentExecutorAdapterForTest(host, "executor-panic", &fakeExecutor{
		execute: func(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error) {
			calls++
			panic("execute panic")
		},
		countTokens: func(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error) {
			calls++
			return pluginapi.ExecutorResponse{Payload: []byte("should-not-run")}, nil
		},
	},
		[]sdktranslator.Format{sdktranslator.FormatOpenAI},
		[]sdktranslator.Format{sdktranslator.FormatOpenAI},
	)

	resp, errExecute := adapter.Execute(context.Background(), &coreauth.Auth{}, coreexecutor.Request{}, coreexecutor.Options{})
	if errExecute == nil {
		t.Fatal("Execute() error = nil, want panic converted to error")
	}
	if len(resp.Payload) != 0 {
		t.Fatalf("Execute() response = %#v, want zero response", resp)
	}
	if !host.isPluginFused("executor-panic") {
		t.Fatal("executor-panic was not fused")
	}
	if calls != 1 {
		t.Fatalf("plugin calls after first Execute() = %d, want 1", calls)
	}

	count, errCountTokens := adapter.CountTokens(context.Background(), &coreauth.Auth{}, coreexecutor.Request{}, coreexecutor.Options{})
	if errCountTokens == nil {
		t.Fatal("CountTokens() error after fuse = nil, want unavailable error")
	}
	if len(count.Payload) != 0 {
		t.Fatalf("CountTokens() response after fuse = %#v, want zero response", count)
	}
	if calls != 1 {
		t.Fatalf("plugin calls after fused CountTokens() = %d, want 1", calls)
	}
}

func TestMapExecutorStreamChunksExitsWhenContextCanceledWithoutDownstreamConsumer(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	in := make(chan pluginapi.ExecutorStreamChunk)
	out := mapExecutorStreamChunks(ctx, in)
	sent := make(chan struct{})

	go func() {
		in <- pluginapi.ExecutorStreamChunk{Payload: []byte("chunk")}
		close(sent)
	}()

	select {
	case <-sent:
	case <-time.After(100 * time.Millisecond):
		t.Fatal("input chunk was not accepted by bridge")
	}
	cancel()
	time.Sleep(10 * time.Millisecond)

	select {
	case chunk, ok := <-out:
		if ok {
			t.Fatalf("output channel produced chunk after cancel: %#v", chunk)
		}
	case <-time.After(100 * time.Millisecond):
		t.Fatal("output channel was not closed after context cancellation")
	}
}

func newHostWithRecords(records ...capabilityRecord) *Host {
	host := New()
	setHostSnapshotForTest(host, true, records...)
	return host
}

func setHostSnapshotForTest(host *Host, enabled bool, records ...capabilityRecord) {
	records = normalizeTestCapabilityRecords(records)
	sortRecords(records)
	host.mu.Lock()
	host.rebuildActivePluginMapsLocked(records)
	host.snapshot.Store(&Snapshot{enabled: enabled, records: records, quotaSupportedProviders: make(map[string][]string)})
	host.mu.Unlock()
}

func newAccessAdapterForTest(host *Host, pluginID string, provider pluginapi.FrontendAuthProvider) *accessAdapter {
	record := normalizeTestCapabilityRecord(capabilityRecord{id: pluginID})
	setHostSnapshotForTest(host, true, record)
	return &accessAdapter{
		host:     host,
		pluginID: pluginID,
		path:     record.path,
		version:  record.version,
		provider: provider,
	}
}

func newCurrentExecutorAdapterForTest(host *Host, pluginID string, executor pluginapi.ProviderExecutor, inputFormats, outputFormats []sdktranslator.Format) *executorAdapter {
	record := normalizeTestCapabilityRecord(capabilityRecord{id: pluginID})
	setHostSnapshotForTest(host, true, record)
	return newExecutorAdapterForRecordForTest(host, record, executor, inputFormats, outputFormats)
}

func newExecutorAdapterForRecordForTest(host *Host, record capabilityRecord, executor pluginapi.ProviderExecutor, inputFormats, outputFormats []sdktranslator.Format) *executorAdapter {
	record = normalizeTestCapabilityRecord(record)
	return &executorAdapter{
		host:          host,
		pluginID:      record.id,
		path:          record.path,
		version:       record.version,
		provider:      "plugin-provider",
		executor:      executor,
		inputFormats:  inputFormats,
		outputFormats: outputFormats,
	}
}

func normalizeTestCapabilityRecord(record capabilityRecord) capabilityRecord {
	id := strings.TrimSpace(record.id)
	if id == "" {
		return record
	}
	if strings.TrimSpace(record.path) == "" {
		record.path = fmt.Sprintf("testdata/%s.plugin", id)
	}
	if strings.TrimSpace(record.version) == "" {
		version := strings.TrimSpace(record.meta.Version)
		if version == "" {
			version = "test-version"
		}
		record.version = version
	}
	return record
}

func normalizeTestCapabilityRecords(records []capabilityRecord) []capabilityRecord {
	out := make([]capabilityRecord, len(records))
	copy(out, records)
	for i := range out {
		out[i] = normalizeTestCapabilityRecord(out[i])
	}
	return out
}

type stringSliceAlias []string

type mapSliceAlias []map[string]string

type requestNormalizerFunc func(context.Context, pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error)

func (f requestNormalizerFunc) NormalizeRequest(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
	return f(ctx, req)
}

type requestTranslatorFunc func(context.Context, pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error)

func (f requestTranslatorFunc) TranslateRequest(ctx context.Context, req pluginapi.RequestTransformRequest) (pluginapi.PayloadResponse, error) {
	return f(ctx, req)
}

type responseNormalizerFunc func(context.Context, pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error)

func (f responseNormalizerFunc) NormalizeResponse(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
	return f(ctx, req)
}

type responseTranslatorFunc func(context.Context, pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error)

func (f responseTranslatorFunc) TranslateResponse(ctx context.Context, req pluginapi.ResponseTransformRequest) (pluginapi.PayloadResponse, error) {
	return f(ctx, req)
}

type usagePluginFunc func(context.Context, pluginapi.UsageRecord)

func (f usagePluginFunc) HandleUsage(ctx context.Context, record pluginapi.UsageRecord) {
	f(ctx, record)
}

type coreUsagePluginFunc func(context.Context, coreusage.Record)

func (f coreUsagePluginFunc) HandleUsage(ctx context.Context, record coreusage.Record) {
	f(ctx, record)
}

type frontendAuthProviderFunc struct {
	identifier   string
	authenticate func(context.Context, pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error)
}

func (f frontendAuthProviderFunc) Identifier() string {
	return f.identifier
}

func (f frontendAuthProviderFunc) Authenticate(ctx context.Context, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
	return f.authenticate(ctx, req)
}

type panicFrontendAuthProvider struct{}

func (panicFrontendAuthProvider) Identifier() string {
	panic("identifier panic")
}

func (panicFrontendAuthProvider) Authenticate(ctx context.Context, req pluginapi.FrontendAuthRequest) (pluginapi.FrontendAuthResponse, error) {
	return pluginapi.FrontendAuthResponse{}, nil
}

type fakeAuthProvider struct {
	identifier          string
	identifiers         []string
	entries             []pluginapi.AuthProviderEntry
	credentialProviders []string
	parseAuth           func(context.Context, pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error)
	startLogin          func(context.Context, pluginapi.AuthLoginStartRequest) (pluginapi.AuthLoginStartResponse, error)
	pollLogin           func(context.Context, pluginapi.AuthLoginPollRequest) (pluginapi.AuthLoginPollResponse, error)
	refreshAuth         func(context.Context, pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error)
}

func (p fakeAuthProvider) Identifier() string {
	return p.identifier
}

func (p fakeAuthProvider) Identifiers() []string {
	return append([]string(nil), p.identifiers...)
}

func (p fakeAuthProvider) AuthEntries() []pluginapi.AuthProviderEntry {
	return append([]pluginapi.AuthProviderEntry(nil), p.entries...)
}

func (p fakeAuthProvider) CredentialProviders() []string {
	return append([]string(nil), p.credentialProviders...)
}

func (p fakeAuthProvider) ParseAuth(ctx context.Context, req pluginapi.AuthParseRequest) (pluginapi.AuthParseResponse, error) {
	if p.parseAuth == nil {
		return pluginapi.AuthParseResponse{}, nil
	}
	return p.parseAuth(ctx, req)
}

func (p fakeAuthProvider) StartLogin(ctx context.Context, req pluginapi.AuthLoginStartRequest) (pluginapi.AuthLoginStartResponse, error) {
	if p.startLogin == nil {
		return pluginapi.AuthLoginStartResponse{}, nil
	}
	return p.startLogin(ctx, req)
}

func (p fakeAuthProvider) PollLogin(ctx context.Context, req pluginapi.AuthLoginPollRequest) (pluginapi.AuthLoginPollResponse, error) {
	if p.pollLogin == nil {
		return pluginapi.AuthLoginPollResponse{}, nil
	}
	return p.pollLogin(ctx, req)
}

func (p fakeAuthProvider) RefreshAuth(ctx context.Context, req pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
	if p.refreshAuth == nil {
		return pluginapi.AuthRefreshResponse{}, nil
	}
	return p.refreshAuth(ctx, req)
}

type modelRegistrarFunc func(context.Context, pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error)

func (f modelRegistrarFunc) RegisterModels(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
	return f(ctx, req)
}

type modelProviderFunc struct {
	staticModels  func(context.Context, pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error)
	modelsForAuth func(context.Context, pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error)
}

func (f modelProviderFunc) StaticModels(ctx context.Context, req pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error) {
	if f.staticModels == nil {
		return pluginapi.ModelResponse{}, nil
	}
	return f.staticModels(ctx, req)
}

func (f modelProviderFunc) ModelsForAuth(ctx context.Context, req pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error) {
	if f.modelsForAuth == nil {
		return pluginapi.ModelResponse{}, nil
	}
	return f.modelsForAuth(ctx, req)
}

func staticModelRegistrar(provider, modelID string) pluginapi.ModelRegistrar {
	return modelRegistrarFunc(func(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
		return pluginapi.ModelRegistrationResponse{
			Provider: provider,
			Models: []pluginapi.ModelInfo{{
				ID: modelID,
			}},
		}, nil
	})
}

func registeredProviderIdentifier(identifier string) bool {
	for _, provider := range sdkaccess.RegisteredProviders() {
		if provider != nil && provider.Identifier() == identifier {
			return true
		}
	}
	return false
}

type fakeModelRegistry struct {
	clients     map[string]*fakeModelClient
	unregisters []string
}

type fakeModelClient struct {
	provider string
	models   []*registry.ModelInfo
}

func newFakeModelRegistry() *fakeModelRegistry {
	return &fakeModelRegistry{
		clients: make(map[string]*fakeModelClient),
	}
}

func (r *fakeModelRegistry) RegisterClient(clientID, clientProvider string, models []*registry.ModelInfo) {
	r.clients[clientID] = &fakeModelClient{
		provider: clientProvider,
		models:   models,
	}
}

func (r *fakeModelRegistry) UnregisterClient(clientID string) {
	delete(r.clients, clientID)
	r.unregisters = append(r.unregisters, clientID)
}

func (r *fakeModelRegistry) GetModelProviders(modelID string) []string {
	counts := make(map[string]int)
	for _, client := range r.clients {
		if client == nil || client.provider == "" {
			continue
		}
		for _, model := range client.models {
			if model != nil && model.ID == modelID {
				counts[client.provider]++
			}
		}
	}
	providers := make([]string, 0, len(counts))
	for provider := range counts {
		providers = append(providers, provider)
	}
	sort.Strings(providers)
	return providers
}

type fakeExecutorManager struct {
	executors     map[string]coreauth.ProviderExecutor
	registerCalls int
	unregisters   []string
	onExecutor    func(string)
	onRegister    func(coreauth.ProviderExecutor)
	onUnregister  func(string)
}

func newFakeExecutorManager() *fakeExecutorManager {
	return &fakeExecutorManager{
		executors: make(map[string]coreauth.ProviderExecutor),
	}
}

func (m *fakeExecutorManager) Executor(provider string) (coreauth.ProviderExecutor, bool) {
	if m.onExecutor != nil {
		m.onExecutor(provider)
	}
	executor, okExecutor := m.executors[provider]
	return executor, okExecutor
}

func (m *fakeExecutorManager) RegisterExecutor(executor coreauth.ProviderExecutor) {
	if m.onRegister != nil {
		m.onRegister(executor)
	}
	m.registerCalls++
	m.executors[executor.Identifier()] = executor
}

func (m *fakeExecutorManager) UnregisterExecutor(provider string) {
	if m.onUnregister != nil {
		m.onUnregister(provider)
	}
	delete(m.executors, provider)
	m.unregisters = append(m.unregisters, provider)
}

type fakeProviderExecutor struct {
	provider string
}

func (e *fakeProviderExecutor) Identifier() string {
	return e.provider
}

func (e *fakeProviderExecutor) Execute(ctx context.Context, auth *coreauth.Auth, req coreexecutor.Request, opts coreexecutor.Options) (coreexecutor.Response, error) {
	return coreexecutor.Response{}, nil
}

func (e *fakeProviderExecutor) ExecuteStream(ctx context.Context, auth *coreauth.Auth, req coreexecutor.Request, opts coreexecutor.Options) (*coreexecutor.StreamResult, error) {
	return nil, nil
}

func (e *fakeProviderExecutor) Refresh(ctx context.Context, auth *coreauth.Auth) (*coreauth.Auth, error) {
	return auth, nil
}

func (e *fakeProviderExecutor) CountTokens(ctx context.Context, auth *coreauth.Auth, req coreexecutor.Request, opts coreexecutor.Options) (coreexecutor.Response, error) {
	return coreexecutor.Response{}, nil
}

func (e *fakeProviderExecutor) HttpRequest(ctx context.Context, auth *coreauth.Auth, req *http.Request) (*http.Response, error) {
	return nil, nil
}

type fakeExecutor struct {
	identifier      string
	identifierFunc  func() string
	panicIdentifier bool
	execute         func(context.Context, pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error)
	executeStream   func(context.Context, pluginapi.ExecutorRequest) (pluginapi.ExecutorStreamResponse, error)
	countTokens     func(context.Context, pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error)
	httpRequest     func(context.Context, pluginapi.ExecutorHTTPRequest) (pluginapi.ExecutorHTTPResponse, error)
}

func (e *fakeExecutor) Identifier() string {
	if e.panicIdentifier {
		panic("identifier panic")
	}
	if e.identifierFunc != nil {
		return e.identifierFunc()
	}
	return e.identifier
}

func (e *fakeExecutor) Execute(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error) {
	return e.execute(ctx, req)
}

func (e *fakeExecutor) ExecuteStream(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorStreamResponse, error) {
	return e.executeStream(ctx, req)
}

func (e *fakeExecutor) CountTokens(ctx context.Context, req pluginapi.ExecutorRequest) (pluginapi.ExecutorResponse, error) {
	return e.countTokens(ctx, req)
}

func (e *fakeExecutor) HttpRequest(ctx context.Context, req pluginapi.ExecutorHTTPRequest) (pluginapi.ExecutorHTTPResponse, error) {
	if e.httpRequest == nil {
		return pluginapi.ExecutorHTTPResponse{}, nil
	}
	return e.httpRequest(ctx, req)
}

func assertExecutorRequest(t *testing.T, req pluginapi.ExecutorRequest) {
	t.Helper()
	if req.AuthID != "auth-1" || req.AuthProvider != "plugin-provider" || req.Model != "model-1" || req.Format != sdktranslator.FormatOpenAI.String() ||
		!req.Stream || req.Alt != "alt" || req.Headers.Get("X-Request") != "yes" || string(req.OriginalRequest) != "original" ||
		req.SourceFormat != sdktranslator.FormatOpenAI.String() || string(req.Payload) != "payload" ||
		req.Metadata["req"] != "metadata" || req.Metadata["opt"] != "metadata" {
		t.Fatalf("executor request = %#v, want mapped request", req)
	}
}

type failingReadCloser struct{}

func (failingReadCloser) Read(p []byte) (int, error) {
	copy(p, []byte("partial"))
	return len("partial"), errors.New("read failed")
}

func (failingReadCloser) Close() error {
	return nil
}

func TestInterceptRequest_ReadOnlyInterceptorDoesNotReplaceBody_Issue6101(t *testing.T) {
	// A read-only interceptor (like cpa-account-config-manager) returns an unmodified body (empty/nil Body).
	// Per RequestInterceptResponse documentation, "Body replaces the current request body only when non-empty."
	// When an interceptor does not mutate the body, host.InterceptRequestBeforeAuth and InterceptRequestAfterAuth
	// must return an empty/nil Body so downstream callers know the request body was not replaced,
	// rather than returning a cloned copy of the input body.
	host := newHostWithRecords(capabilityRecord{
		id: "read-only-observer",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
				return pluginapi.RequestInterceptResponse{
					Headers: http.Header{"X-Observed": []string{"true"}},
				}, nil
			}),
		}},
	})

	inputBody := []byte("large-request-payload-for-codex-session")
	gotBefore := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{
		Body: inputBody,
	})
	if len(gotBefore.Body) != 0 {
		t.Fatalf("InterceptRequestBeforeAuth returned non-empty body %q, want empty body for read-only interceptor", gotBefore.Body)
	}
	if gotBefore.Headers.Get("X-Observed") != "true" {
		t.Fatalf("expected header X-Observed to be preserved, got %#v", gotBefore.Headers)
	}

	gotAfter := host.InterceptRequestAfterAuth(context.Background(), pluginapi.RequestInterceptRequest{
		Body: inputBody,
	})
	if len(gotAfter.Body) != 0 {
		t.Fatalf("InterceptRequestAfterAuth returned non-empty body %q, want empty body for read-only interceptor", gotAfter.Body)
	}
}

func TestInterceptRequest_FirstPluginInPlaceMutationDiscardedIfEmptyOrFailed(t *testing.T) {
	// If plugin 1 mutates req.Body in-place but returns an empty body (or fails),
	// plugin 2 must still receive the uncorrupted original body from currentBase.
	var plugin2ReceivedBody []byte
	host := newHostWithRecords(
		capabilityRecord{
			id:       "plugin-1-mutator",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					// In-place mutation on nextReq.Body
					req.Body[0] = 'X'
					// Returns empty body (mutation is not returned as a replacement)
					return pluginapi.RequestInterceptResponse{
						Headers: http.Header{"X-Plugin-1": []string{"ran"}},
					}, nil
				}),
			}},
		},
		capabilityRecord{
			id:       "plugin-2-observer",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					plugin2ReceivedBody = bytes.Clone(req.Body)
					return pluginapi.RequestInterceptResponse{}, nil
				}),
			}},
		},
	)

	inputBody := []byte("clean-payload")
	got := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{
		Body: inputBody,
	})

	if string(inputBody) != "clean-payload" {
		t.Fatalf("caller inputBody was corrupted: %q", string(inputBody))
	}
	if string(plugin2ReceivedBody) != "clean-payload" {
		t.Fatalf("plugin 2 received corrupted body: %q, want clean-payload", string(plugin2ReceivedBody))
	}
	if len(got.Body) != 0 {
		t.Fatalf("got.Body should be empty when no plugin returned non-empty body, got: %q", string(got.Body))
	}
}

func TestInterceptRequest_FirstPluginInPlaceMutationDiscardedOnError(t *testing.T) {
	// If plugin 1 mutates req.Body in-place but returns an error,
	// plugin 2 must still receive the uncorrupted original body from currentBase.
	var plugin2ReceivedBody []byte
	host := newHostWithRecords(
		capabilityRecord{
			id:       "plugin-1-error",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					req.Body[0] = 'E'
					return pluginapi.RequestInterceptResponse{}, errors.New("interceptor failed")
				}),
			}},
		},
		capabilityRecord{
			id:       "plugin-2-observer",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					plugin2ReceivedBody = bytes.Clone(req.Body)
					return pluginapi.RequestInterceptResponse{}, nil
				}),
			}},
		},
	)

	inputBody := []byte("clean-payload")
	got := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{
		Body: inputBody,
	})

	if string(inputBody) != "clean-payload" {
		t.Fatalf("caller inputBody was corrupted: %q", string(inputBody))
	}
	if string(plugin2ReceivedBody) != "clean-payload" {
		t.Fatalf("plugin 2 received corrupted body: %q, want clean-payload", string(plugin2ReceivedBody))
	}
	if len(got.Body) != 0 {
		t.Fatalf("got.Body should be empty when no plugin returned non-empty body, got: %q", string(got.Body))
	}
}

func TestInterceptRequest_FirstPluginInPlaceMutationDiscardedOnPanic(t *testing.T) {
	var plugin2ReceivedBody []byte
	host := newHostWithRecords(
		capabilityRecord{
			id:       "plugin-1-panic",
			priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					req.Body[0] = 'Z'
					panic("boom")
				}),
			}},
		},
		capabilityRecord{
			id:       "plugin-2-observer",
			priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					plugin2ReceivedBody = bytes.Clone(req.Body)
					return pluginapi.RequestInterceptResponse{}, nil
				}),
			}},
		},
	)

	inputBody := []byte("clean-payload")
	got := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{
		Body: inputBody,
	})

	if string(inputBody) != "clean-payload" {
		t.Fatalf("caller inputBody was corrupted: %q", string(inputBody))
	}
	if string(plugin2ReceivedBody) != "clean-payload" {
		t.Fatalf("plugin 2 received corrupted body: %q, want clean-payload", string(plugin2ReceivedBody))
	}
	if len(got.Body) != 0 {
		t.Fatalf("got.Body should be empty when no plugin returned non-empty body, got: %q", string(got.Body))
	}
}

func BenchmarkHostRequestInterceptors_ReadOnly(b *testing.B) {
	sizes := []struct {
		name  string
		bytes int
	}{
		{name: "1MiB", bytes: 1 << 20},
		{name: "8MiB", bytes: 8 << 20},
	}
	// 1 read-only plugin (matching cpa-account-config-manager)
	hostSingle := newHostWithRecords(capabilityRecord{
		id: "read-only-observer",
		plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
			RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
				return pluginapi.RequestInterceptResponse{}, nil
			}),
		}},
	})
	// 2 read-only plugins
	hostMulti := newHostWithRecords(
		capabilityRecord{
			id: "observer-1", priority: 20,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					return pluginapi.RequestInterceptResponse{}, nil
				}),
			}},
		},
		capabilityRecord{
			id: "observer-2", priority: 10,
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				RequestInterceptor: requestInterceptorFunc(func(ctx context.Context, req pluginapi.RequestInterceptRequest) (pluginapi.RequestInterceptResponse, error) {
					return pluginapi.RequestInterceptResponse{}, nil
				}),
			}},
		},
	)

	for _, size := range sizes {
		payload := make([]byte, size.bytes)
		b.Run("single/"+size.name, func(b *testing.B) {
			b.ReportAllocs()
			b.ResetTimer()
			for range b.N {
				got := hostSingle.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{Body: payload})
				if len(got.Body) != 0 {
					b.Fatal("expected empty body for read-only interceptor")
				}
			}
		})
		b.Run("multi/"+size.name, func(b *testing.B) {
			b.ReportAllocs()
			b.ResetTimer()
			for range b.N {
				got := hostMulti.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{Body: payload})
				if len(got.Body) != 0 {
					b.Fatal("expected empty body for read-only interceptor")
				}
			}
		})
	}
}

func TestExecutorAdapterRefresh_MergesAttributesAndPreservesPath_Issue6119(t *testing.T) {
	filePath := filepath.Join(t.TempDir(), "plugin.json")
	authProvider := fakeAuthProvider{
		identifier: "plugin-provider",
		refreshAuth: func(ctx context.Context, req pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
			return pluginapi.AuthRefreshResponse{
				Auth: pluginapi.AuthData{
					Metadata:   map[string]any{"token": "new"},
					Attributes: map[string]string{"priority": "2"},
				},
			}, nil
		},
	}
	executorRecord := normalizeTestCapabilityRecord(capabilityRecord{id: "executor-plugin"})
	host := newHostWithRecords(
		capabilityRecord{
			id: "auth-plugin",
			plugin: pluginapi.Plugin{
				Capabilities: pluginapi.Capabilities{
					AuthProvider: authProvider,
				},
			},
		},
		executorRecord,
	)

	exec := &fakeExecutor{identifier: "ignored-by-adapter"}
	adapter := newExecutorAdapterForRecordForTest(host, executorRecord, exec,
		[]sdktranslator.Format{sdktranslator.FormatOpenAI},
		[]sdktranslator.Format{sdktranslator.FormatOpenAI},
	)

	auth := &coreauth.Auth{
		ID:       "auth-1",
		Provider: "plugin-provider",
		FileName: "plugin.json",
		Attributes: map[string]string{
			coreauth.AttributeSourceBackend: coreauth.AuthSourceFile,
			coreauth.AttributePath:          filePath,
			coreauth.AttributeSource:        filePath,
			"custom_env":                    "staging",
		},
		Metadata: map[string]any{"token": "old"},
	}

	refreshed, errRefresh := adapter.Refresh(context.Background(), auth)
	if errRefresh != nil {
		t.Fatalf("Refresh() error = %v", errRefresh)
	}
	if got := refreshed.Attributes[coreauth.AttributePath]; got != filePath {
		t.Errorf("refreshed path attribute = %q, want %q", got, filePath)
	}
	if got := refreshed.Attributes[coreauth.AttributeSource]; got != filePath {
		t.Errorf("refreshed source attribute = %q, want %q", got, filePath)
	}
	if got := refreshed.Attributes[coreauth.AttributeSourceBackend]; got != coreauth.AuthSourceFile {
		t.Errorf("refreshed source_backend attribute = %q, want %q", got, coreauth.AuthSourceFile)
	}
	if got := refreshed.Attributes["custom_env"]; got != "staging" {
		t.Errorf("refreshed custom_env attribute = %q, want staging", got)
	}
	if got := refreshed.Attributes["priority"]; got != "2" {
		t.Errorf("refreshed priority attribute = %q, want 2", got)
	}
}

func TestExecutorAdapterRefresh_PreservesDisabledState_Issue7225(t *testing.T) {
	authProvider := fakeAuthProvider{
		identifier: "plugin-provider",
		refreshAuth: func(ctx context.Context, req pluginapi.AuthRefreshRequest) (pluginapi.AuthRefreshResponse, error) {
			return pluginapi.AuthRefreshResponse{
				Auth: pluginapi.AuthData{
					Metadata: map[string]any{"token": "new"},
				},
			}, nil
		},
	}
	executorRecord := normalizeTestCapabilityRecord(capabilityRecord{id: "executor-plugin"})
	host := newHostWithRecords(
		capabilityRecord{
			id: "auth-plugin",
			plugin: pluginapi.Plugin{
				Capabilities: pluginapi.Capabilities{
					AuthProvider: authProvider,
				},
			},
		},
		executorRecord,
	)

	exec := &fakeExecutor{identifier: "ignored-by-adapter"}
	adapter := newExecutorAdapterForRecordForTest(host, executorRecord, exec,
		[]sdktranslator.Format{sdktranslator.FormatOpenAI},
		[]sdktranslator.Format{sdktranslator.FormatOpenAI},
	)

	auth := &coreauth.Auth{
		ID:       "auth-1",
		Provider: "plugin-provider",
		Disabled: true,
		Status:   coreauth.StatusDisabled,
		Metadata: map[string]any{"token": "old"},
	}

	refreshed, errRefresh := adapter.Refresh(context.Background(), auth)
	if errRefresh != nil {
		t.Fatalf("Refresh() error = %v", errRefresh)
	}
	if refreshed == nil {
		t.Fatal("Refresh() = nil, want refreshed auth")
	}
	if !refreshed.Disabled || refreshed.Status != coreauth.StatusDisabled {
		t.Fatalf("Refresh() disabled=%t status=%q, want disabled auth to stay disabled", refreshed.Disabled, refreshed.Status)
	}
	if got := refreshed.Metadata["token"]; got != "new" {
		t.Fatalf("Refresh() token = %q, want new", got)
	}
}

func TestUsageAdapterPropagatesRequestIDTraceIDAndSelectionID(t *testing.T) {
	var captured pluginapi.UsageRecord
	record := normalizeTestCapabilityRecord(capabilityRecord{
		id: "usage-selection-test",
		plugin: pluginapi.Plugin{
			Capabilities: pluginapi.Capabilities{
				UsagePlugin: usagePluginFunc(func(ctx context.Context, rec pluginapi.UsageRecord) {
					captured = rec
				}),
			},
		},
	})
	host := newHostWithRecords(record)
	adapter := &usageAdapter{
		host:     host,
		pluginID: record.id,
		plugin:   record.plugin.Capabilities.UsagePlugin,
	}

	adapter.HandleUsage(context.Background(), coreusage.Record{
		Provider:    "openai",
		Model:       "gpt-5.4",
		RequestID:   "attempt-req-1",
		TraceID:     "trace-1",
		SelectionID: "sel-explicit-1",
	})
	if captured.SelectionID != "sel-explicit-1" {
		t.Fatalf("captured.SelectionID = %q, want %q", captured.SelectionID, "sel-explicit-1")
	}
	if captured.RequestID != "attempt-req-1" || captured.TraceID != "trace-1" {
		t.Fatalf("captured RequestID=%q TraceID=%q, want attempt-req-1 / trace-1", captured.RequestID, captured.TraceID)
	}

	ctxWithSel := coreusage.WithSelectionID(context.Background(), "sel-from-ctx")
	adapter.HandleUsage(ctxWithSel, coreusage.Record{
		Provider: "openai",
		Model:    "gpt-5.4",
	})
	if captured.SelectionID != "sel-from-ctx" {
		t.Fatalf("captured.SelectionID from context fallback = %q, want %q", captured.SelectionID, "sel-from-ctx")
	}
}

type funcRequestBodyPatcher func(context.Context, []byte) (pluginapi.RequestBodyPatchResponse, error)

func (f funcRequestBodyPatcher) PatchRequestBody(ctx context.Context, body []byte) (pluginapi.RequestBodyPatchResponse, error) {
	return f(ctx, body)
}

func TestInterceptRequestBodyPatchValidationAndSequentialChaining(t *testing.T) {
	rawInput := []byte(`{"model":"gpt-5","input":[{"role":"developer","content":[{"type":"input_text","text":"<environment_context>\n  <timezone>UTC</timezone>\n  <current_date>2026-04-19</current_date>\n</environment_context>"}]}]}`)

	// Plugin 1: replaces <timezone>UTC</timezone> -> <timezone>Asia/Shanghai</timezone> (+10 bytes),
	// shifting the byte offset of <current_date>2026-04-19</current_date> in the resulting body.
	plugin1 := normalizeTestCapabilityRecord(capabilityRecord{
		id: "01-tz-plugin",
		plugin: pluginapi.Plugin{
			SchemaVersion: pluginabi.SchemaVersionRequestInterceptorBodyPatch,
			Capabilities: pluginapi.Capabilities{
				RequestBodyPatcherBefore: funcRequestBodyPatcher(func(_ context.Context, body []byte) (pluginapi.RequestBodyPatchResponse, error) {
					target := []byte("<timezone>UTC</timezone>")
					idx := bytes.Index(body, target)
					if idx < 0 {
						return pluginapi.RequestBodyPatchResponse{}, nil
					}
					return pluginapi.RequestBodyPatchResponse{
						Edits: []pluginapi.RequestBodyEdit{
							{
								Start:       idx,
								End:         idx + len(target),
								Replacement: "<timezone>Asia/Shanghai</timezone>",
							},
						},
					}, nil
				}),
			},
		},
	})

	// Plugin 2: receives Plugin 1's patched body and returns byte-range edits relative to the body it received.
	plugin2 := normalizeTestCapabilityRecord(capabilityRecord{
		id: "02-date-plugin",
		plugin: pluginapi.Plugin{
			SchemaVersion: pluginabi.SchemaVersionRequestInterceptorBodyPatch,
			Capabilities: pluginapi.Capabilities{
				RequestBodyPatcherBefore: funcRequestBodyPatcher(func(_ context.Context, body []byte) (pluginapi.RequestBodyPatchResponse, error) {
					if !bytes.Contains(body, []byte("<timezone>Asia/Shanghai</timezone>")) {
						t.Fatalf("plugin2 did not receive plugin1's patched body: %s", body)
					}
					target := []byte("<current_date>2026-04-19</current_date>")
					idx := bytes.Index(body, target)
					if idx < 0 {
						return pluginapi.RequestBodyPatchResponse{}, nil
					}
					return pluginapi.RequestBodyPatchResponse{
						Edits: []pluginapi.RequestBodyEdit{
							{
								Start:       idx,
								End:         idx + len(target),
								Replacement: "<current_date>2026-04-20</current_date>",
							},
						},
					}, nil
				}),
			},
		},
	})

	// Plugin 3: returns invalid/overlapping/out-of-bounds edits or invalid JSON; host must reject and preserve Plugin 2's valid body.
	var invalidCase int
	plugin3 := normalizeTestCapabilityRecord(capabilityRecord{
		id: "03-invalid-edits-plugin",
		plugin: pluginapi.Plugin{
			SchemaVersion: pluginabi.SchemaVersionRequestInterceptorBodyPatch,
			Capabilities: pluginapi.Capabilities{
				RequestBodyPatcherBefore: funcRequestBodyPatcher(func(_ context.Context, body []byte) (pluginapi.RequestBodyPatchResponse, error) {
					switch invalidCase {
					case 0: // Out of bounds
						return pluginapi.RequestBodyPatchResponse{
							Edits: []pluginapi.RequestBodyEdit{{Start: 0, End: len(body) + 5, Replacement: "{}"}},
						}, nil
					case 1: // Overlapping ranges
						return pluginapi.RequestBodyPatchResponse{
							Edits: []pluginapi.RequestBodyEdit{
								{Start: 2, End: 8, Replacement: `"a"`},
								{Start: 5, End: 10, Replacement: `"b"`},
							},
						}, nil
					case 2: // Invalid JSON after splice
						return pluginapi.RequestBodyPatchResponse{
							Edits: []pluginapi.RequestBodyEdit{{Start: 0, End: 1, Replacement: "not-json"}},
						}, nil
					default:
						return pluginapi.RequestBodyPatchResponse{}, nil
					}
				}),
			},
		},
	})

	host := newHostWithRecords(plugin1, plugin2, plugin3)
	wantChained := `{"model":"gpt-5","input":[{"role":"developer","content":[{"type":"input_text","text":"<environment_context>\n  <timezone>Asia/Shanghai</timezone>\n  <current_date>2026-04-20</current_date>\n</environment_context>"}]}]}`

	for invalidCase = 0; invalidCase <= 2; invalidCase++ {
		resp := host.InterceptRequestBeforeAuth(context.Background(), pluginapi.RequestInterceptRequest{
			Model: "gpt-5",
			Body:  rawInput,
		})
		if string(resp.Body) != wantChained {
			t.Fatalf("case %d: chained patched body = %s, want %s", invalidCase, resp.Body, wantChained)
		}
	}
}

func TestRegisterModelsMultiProviderRegistrationsAndUnload(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	host := New()
	t.Cleanup(host.ShutdownAll)

	host.RegisterPluginForTest("catalog-multi", pluginapi.Plugin{
		Capabilities: pluginapi.Capabilities{
			ModelRegistrar: modelRegistrarFunc(func(ctx context.Context, req pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
				if !req.MultiProviderModels {
					t.Fatalf("req.MultiProviderModels = false, want true")
				}
				return pluginapi.ModelRegistrationResponse{
					Provider: "compat-direct",
					Models:   []pluginapi.ModelInfo{{ID: "legacy-fallback-model"}},
					Registrations: []pluginapi.ProviderModels{
						{
							Provider: "  Compat-Direct  ",
							Models:   []pluginapi.ModelInfo{{ID: " model-direct "}},
						},
						{
							Provider: "  Compat-VPN  ",
							Models:   []pluginapi.ModelInfo{{ID: " model-vpn "}},
						},
					},
				}, nil
			}),
		},
	})
	host.RegisterPluginForTest("auth-multi", pluginapi.Plugin{
		Capabilities: pluginapi.Capabilities{
			ModelProvider: modelProviderFunc{
				staticModels: func(ctx context.Context, req pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error) {
					if !req.MultiProviderModels {
						t.Fatalf("req.MultiProviderModels = false, want true")
					}
					return pluginapi.ModelResponse{
						Registrations: []pluginapi.ProviderModels{
							{
								Provider: "  Compat-Auth-Direct  ",
								Models:   []pluginapi.ModelInfo{{ID: " static-auth-direct "}},
							},
							{
								Provider: "  Compat-Auth-VPN  ",
								Models:   []pluginapi.ModelInfo{{ID: " static-auth-vpn "}},
							},
						},
					}, nil
				},
				modelsForAuth: func(ctx context.Context, req pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error) {
					switch strings.ToLower(strings.TrimSpace(req.AuthProvider)) {
					case "compat-auth-direct":
						return pluginapi.ModelResponse{
							Provider: "compat-auth-direct",
							Models:   []pluginapi.ModelInfo{{ID: "auth-model-direct"}},
						}, nil
					case "compat-auth-vpn":
						return pluginapi.ModelResponse{
							Provider: "compat-auth-vpn",
							Models:   []pluginapi.ModelInfo{{ID: "auth-model-vpn"}},
						}, nil
					default:
						return pluginapi.ModelResponse{}, nil
					}
				},
			},
		},
	})

	host.RegisterModels(context.Background(), modelRegistry)
	host.RegisterExecutors(manager, modelRegistry)

	if models := host.ModelsForProvider("compat-direct"); len(models) != 1 || models[0].ID != "model-direct" {
		t.Fatalf("compat-direct models = %#v, want [model-direct]", models)
	}
	if models := host.ModelsForProvider("compat-vpn"); len(models) != 1 || models[0].ID != "model-vpn" {
		t.Fatalf("compat-vpn models = %#v, want [model-vpn]", models)
	}

	if !host.HasAuthModelProvider("compat-auth-direct") || !host.HasAuthModelProvider("compat-auth-vpn") {
		t.Fatalf("HasAuthModelProvider = (%v, %v), want (true, true)", host.HasAuthModelProvider("compat-auth-direct"), host.HasAuthModelProvider("compat-auth-vpn"))
	}
	directAuthRes := host.ModelsForAuth(context.Background(), &coreauth.Auth{ID: "auth-1", Provider: "compat-auth-direct"})
	if !directAuthRes.Handled || len(directAuthRes.Models) != 1 || directAuthRes.Models[0].ID != "auth-model-direct" {
		t.Fatalf("ModelsForAuth(compat-auth-direct) = %#v, want handled with auth-model-direct", directAuthRes)
	}
	vpnAuthRes := host.ModelsForAuth(context.Background(), &coreauth.Auth{ID: "auth-2", Provider: "compat-auth-vpn"})
	if !vpnAuthRes.Handled || len(vpnAuthRes.Models) != 1 || vpnAuthRes.Models[0].ID != "auth-model-vpn" {
		t.Fatalf("ModelsForAuth(compat-auth-vpn) = %#v, want handled with auth-model-vpn", vpnAuthRes)
	}

	if !host.UnloadPlugin("catalog-multi") {
		t.Fatal("UnloadPlugin(catalog-multi) = false, want true")
	}
	if models := host.ModelsForProvider("compat-direct"); len(models) != 0 {
		t.Fatalf("compat-direct models after unload = %#v, want empty", models)
	}
	if models := host.ModelsForProvider("compat-vpn"); len(models) != 0 {
		t.Fatalf("compat-vpn models after unload = %#v, want empty", models)
	}

	if !host.UnloadPlugin("auth-multi") {
		t.Fatal("UnloadPlugin(auth-multi) = false, want true")
	}
	if host.HasAuthModelProvider("compat-auth-direct") || host.HasAuthModelProvider("compat-auth-vpn") {
		t.Fatalf("HasAuthModelProvider after unload = (%v, %v), want (false, false)", host.HasAuthModelProvider("compat-auth-direct"), host.HasAuthModelProvider("compat-auth-vpn"))
	}
}

func TestRegisterModelsLegacyEmptyStaticModelsPreservesSingleProviderBehavior(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	registrarCalled := false
	host := newHostWithRecords(
		capabilityRecord{
			id: "dual-empty-static",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ModelProvider: modelProviderFunc{
					staticModels: func(context.Context, pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error) {
						return pluginapi.ModelResponse{}, nil
					},
					modelsForAuth: func(context.Context, pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error) {
						return pluginapi.ModelResponse{
							Provider: "legacy-provider",
							Models:   []pluginapi.ModelInfo{{ID: "unexpected"}},
						}, nil
					},
				},
				ModelRegistrar: modelRegistrarFunc(func(context.Context, pluginapi.ModelRegistrationRequest) (pluginapi.ModelRegistrationResponse, error) {
					registrarCalled = true
					return pluginapi.ModelRegistrationResponse{
						Provider: "legacy-provider",
						Models:   []pluginapi.ModelInfo{{ID: "legacy-model"}},
					}, nil
				}),
			}},
		},
		capabilityRecord{
			id: "empty-models-provider",
			plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
				ModelProvider: modelProviderFunc{
					staticModels: func(context.Context, pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error) {
						return pluginapi.ModelResponse{Provider: "gemini"}, nil
					},
					modelsForAuth: func(context.Context, pluginapi.AuthModelRequest) (pluginapi.ModelResponse, error) {
						return pluginapi.ModelResponse{Provider: "gemini"}, nil
					},
				},
			}},
		},
	)

	host.RegisterModels(context.Background(), modelRegistry)
	host.RegisterExecutors(manager, modelRegistry)

	if registrarCalled {
		t.Fatal("ModelRegistrar.RegisterModels was called despite ModelProvider being present")
	}
	if models := host.ModelsForProvider("legacy-provider"); len(models) != 0 {
		t.Fatalf("legacy-provider models = %#v, want empty when StaticModels returns empty", models)
	}
	if host.HasAuthModelProvider("legacy-provider") {
		t.Fatal("HasAuthModelProvider(legacy-provider) = true, want false when StaticModels returns empty")
	}
	if host.HasAuthModelProvider("gemini") {
		t.Fatal("HasAuthModelProvider(gemini) = true, want false when StaticModels returns empty models")
	}
	if res := host.ModelsForAuth(context.Background(), &coreauth.Auth{ID: "g1", Provider: "gemini"}); res.Handled {
		t.Fatalf("ModelsForAuth(gemini) = %#v, want unhandled when StaticModels had no models", res)
	}
}

func TestRegisterModelsStaticModelsRegistrationsMergeAndSharedProviderUnload(t *testing.T) {
	modelRegistry := newFakeModelRegistry()
	manager := newFakeExecutorManager()
	host := New()
	t.Cleanup(host.ShutdownAll)

	host.RegisterPluginForTest("alpha", pluginapi.Plugin{
		Capabilities: pluginapi.Capabilities{
			ModelProvider: modelProviderFunc{
				staticModels: func(ctx context.Context, req pluginapi.StaticModelRequest) (pluginapi.ModelResponse, error) {
					if !req.MultiProviderModels {
						t.Fatalf("req.MultiProviderModels = false, want true")
					}
					return pluginapi.ModelResponse{
						Registrations: []pluginapi.ProviderModels{
							{Provider: "shared-provider", Models: []pluginapi.ModelInfo{{ID: "alpha-1"}}},
							{Provider: "shared-provider", Models: []pluginapi.ModelInfo{{ID: "alpha-1"}, {ID: "alpha-2"}}},
							{Provider: "alpha-only", Models: []pluginapi.ModelInfo{{ID: "alpha-only-1"}}},
						},
					}, nil
				},
			},
		},
	})
	host.RegisterPluginForTest("bravo", pluginapi.Plugin{
		Capabilities: pluginapi.Capabilities{
			ModelRegistrar: staticModelRegistrar("shared-provider", "bravo-1"),
		},
	})

	host.RegisterModels(context.Background(), modelRegistry)
	host.RegisterExecutors(manager, modelRegistry)

	shared := host.ModelsForProvider("shared-provider")
	if len(shared) != 3 || shared[0].ID != "alpha-1" || shared[1].ID != "alpha-2" || shared[2].ID != "bravo-1" {
		t.Fatalf("shared-provider models = %#v, want [alpha-1 alpha-2 bravo-1]", shared)
	}
	if alphaOnly := host.ModelsForProvider("alpha-only"); len(alphaOnly) != 1 || alphaOnly[0].ID != "alpha-only-1" {
		t.Fatalf("alpha-only models = %#v, want [alpha-only-1]", alphaOnly)
	}

	if !host.UnloadPlugin("alpha") {
		t.Fatal("UnloadPlugin(alpha) = false, want true")
	}
	sharedAfterUnload := host.ModelsForProvider("shared-provider")
	if len(sharedAfterUnload) != 1 || sharedAfterUnload[0].ID != "bravo-1" {
		t.Fatalf("shared-provider models after unloading alpha = %#v, want [bravo-1]", sharedAfterUnload)
	}
	if alphaOnlyAfterUnload := host.ModelsForProvider("alpha-only"); len(alphaOnlyAfterUnload) != 0 {
		t.Fatalf("alpha-only models after unloading alpha = %#v, want empty", alphaOnlyAfterUnload)
	}
}