fix(pluginhost): detach context and log rpc failures in usage handling

- Detach cancellation from context before dispatching usage records to plugins.
- Log debug messages when RPC `usage.handle` calls fail.

Closes: #5244
This commit is contained in:
Luis Pater
2026-08-27 04:35:51 +08:00
parent 6f6856e784
commit b7f6c15f83
3 changed files with 52 additions and 1 deletions

View File

@@ -2283,6 +2283,49 @@ func TestUsageAdapterPreservesExplicitGenerateFalse(t *testing.T) {
}
}
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()

View File

@@ -136,6 +136,11 @@ func (a *usageAdapter) HandleUsage(ctx context.Context, record coreusage.Record)
if plugin == nil {
return
}
if ctx == nil {
ctx = context.Background()
} else {
ctx = context.WithoutCancel(ctx)
}
defer func() {
if recovered := recover(); recovered != nil {
a.host.fusePlugin(a.pluginID, "UsagePlugin.HandleUsage", recovered)

View File

@@ -11,6 +11,7 @@ import (
"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginabi"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi"
log "github.com/sirupsen/logrus"
)
type rpcPluginAdapter struct {
@@ -539,7 +540,9 @@ func (a rpcThinkingApplier) ApplyThinking(ctx context.Context, req pluginapi.Thi
}
func (a *rpcPluginAdapter) HandleUsage(ctx context.Context, record pluginapi.UsageRecord) {
_, _ = callPlugin[rpcEmptyResponse](ctx, a.client, pluginabi.MethodUsageHandle, record)
if _, errCall := callPlugin[rpcEmptyResponse](ctx, a.client, pluginabi.MethodUsageHandle, record); errCall != nil {
log.Debugf("pluginhost: usage.handle to %s failed: %v", a.id, errCall)
}
}
func (a *rpcPluginAdapter) RegisterCommandLine(ctx context.Context, req pluginapi.CommandLineRegistrationRequest) (pluginapi.CommandLineRegistrationResponse, error) {