From 4bcb67748463a75ff8e42858e128e33c3684765d Mon Sep 17 00:00:00 2001 From: steinder Date: Sun, 30 Aug 2026 15:16:07 -0400 Subject: [PATCH] fix: inference-parser misses /chat/completions from /v1-less proxies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenAI-compatible proxies (e.g. litellm used by opencode) post to /chat/completions without the /v1 prefix. The OnRequest dispatch switch only matched /v1/chat/completions and /v1/completions, so requests fell through to the default arm and pctx.Extensions.Inference was never populated — resulting in missing model, token counts, and method fields in abctl session views. Extend the switch to also match /chat/completions and /completions. No changes to OnResponse or OnResponseFrame — the default branch there already routes to the OpenAI parsers. Add two tests: TestInferenceParser_VlessPath_ChatCompletions and TestInferenceParser_VlessPath_Completions. Fixes #822 Signed-off-by: steinder --- .../authlib/plugins/inferenceparser/plugin.go | 2 +- .../plugins/inferenceparser/plugin_test.go | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/authbridge/authlib/plugins/inferenceparser/plugin.go b/authbridge/authlib/plugins/inferenceparser/plugin.go index 1e0a7ac5..eba10a67 100644 --- a/authbridge/authlib/plugins/inferenceparser/plugin.go +++ b/authbridge/authlib/plugins/inferenceparser/plugin.go @@ -56,7 +56,7 @@ func (p *InferenceParser) OnRequest(_ context.Context, pctx *pipeline.Context) p // "inference-parser is in this pipeline" from config, not per-event rows. var ext *pipeline.InferenceExtension switch endpointPath(pctx) { - case "/v1/chat/completions", "/v1/completions": + case "/v1/chat/completions", "/v1/completions", "/chat/completions", "/completions": ext = parseOpenAIRequest(pctx.Body) case anthropicMessagesPath: ext = parseAnthropicRequest(pctx.Body) diff --git a/authbridge/authlib/plugins/inferenceparser/plugin_test.go b/authbridge/authlib/plugins/inferenceparser/plugin_test.go index 50a29ca6..a8585ce1 100644 --- a/authbridge/authlib/plugins/inferenceparser/plugin_test.go +++ b/authbridge/authlib/plugins/inferenceparser/plugin_test.go @@ -289,6 +289,50 @@ func TestInferenceParser_OnResponse_CapturesToolCalls(t *testing.T) { } } +// TestInferenceParser_VlessPath covers OpenAI-compatible proxies (e.g. opencode via +// litellm) that strip the /v1 prefix and post directly to /chat/completions or +// /completions. Without these path variants the parser falls to the default arm +// and records no inference telemetry. +func TestInferenceParser_VlessPath_ChatCompletions(t *testing.T) { + p := NewInferenceParser() + pctx := &pipeline.Context{ + Path: "/chat/completions", + Body: []byte(`{"model":"gpt-4","messages":[{"role":"user","content":"hi"}],"stream":false}`), + } + action := p.OnRequest(context.Background(), pctx) + if action.Type != pipeline.Continue { + t.Fatalf("expected Continue, got %v", action.Type) + } + ext := pctx.Extensions.Inference + if ext == nil { + t.Fatal("Extensions.Inference is nil for /chat/completions") + } + if ext.Model != "gpt-4" { + t.Errorf("Model = %q, want gpt-4", ext.Model) + } + if !ext.IsAction { + t.Error("IsAction should be true") + } +} + +func TestInferenceParser_VlessPath_Completions(t *testing.T) { + p := NewInferenceParser() + pctx := &pipeline.Context{ + Path: "/completions", + Body: []byte(`{"model":"codellama","messages":[{"role":"user","content":"hi"}]}`), + } + action := p.OnRequest(context.Background(), pctx) + if action.Type != pipeline.Continue { + t.Fatalf("expected Continue, got %v", action.Type) + } + if pctx.Extensions.Inference == nil { + t.Fatal("Extensions.Inference is nil for /completions") + } + if pctx.Extensions.Inference.Model != "codellama" { + t.Errorf("Model = %q, want codellama", pctx.Extensions.Inference.Model) + } +} + func TestInferenceParser_NonMatchingPath(t *testing.T) { p := NewInferenceParser() pctx := &pipeline.Context{