From b56ad080bd4c39e7608e9b89fb0d7ca4dada91d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:28:30 +0000 Subject: [PATCH 1/3] Initial plan From 40a33de588d424eb7c36d0fca4795b02c0586577 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:37:42 +0000 Subject: [PATCH 2/3] refactor: deduplicate jq filter compile pipeline --- internal/middleware/jqschema.go | 57 +++++++++++++++------------------ 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/internal/middleware/jqschema.go b/internal/middleware/jqschema.go index de1c34cea..44c043c8b 100644 --- a/internal/middleware/jqschema.go +++ b/internal/middleware/jqschema.go @@ -253,32 +253,13 @@ func runJqCode( // For parameterized filters that need to incorporate per-call values such as server IDs, // session metadata, or user-controlled data, use CompileToolResponseFilterWithVars instead. func CompileToolResponseFilter(filter string) (*gojq.Code, error) { - if cached, ok := filterCodeCache.Load(filter); ok { - code, ok := cached.(*gojq.Code) - if !ok { - // Should never happen; the cache only stores *gojq.Code values. - return nil, fmt.Errorf("internal error: unexpected cached value type for filter (len=%d)", len(filter)) - } - logMiddleware.Printf("CompileToolResponseFilter: cache hit, len=%d", len(filter)) - return code, nil - } - - logMiddleware.Printf("CompileToolResponseFilter: parsing jq filter expression, len=%d", len(filter)) - query, err := gojq.Parse(filter) - if err != nil { - return nil, fmt.Errorf("failed to parse tool response filter: %w", err) - } - - code, err := gojq.Compile(query, - secureCompileOpts..., + return compileToolResponseFilterInternal( + filter, + filter, + secureCompileOpts, + "CompileToolResponseFilter", + "", ) - if err != nil { - return nil, fmt.Errorf("failed to compile tool response filter: %w", err) - } - - filterCodeCache.Store(filter, code) - logMiddleware.Printf("CompileToolResponseFilter: filter compiled and cached successfully") - return code, nil } // CompileToolResponseFilterWithVars parses and compiles a jq expression that references @@ -300,31 +281,45 @@ func CompileToolResponseFilterWithVars(filter string, varNames []string) (*gojq. filter: filter, varNamesKey: buildVarNamesCacheKey(varNames), } + return compileToolResponseFilterInternal( + cacheKey, + filter, + compileOptsWithVariables(varNames), + "CompileToolResponseFilterWithVars", + fmt.Sprintf(", vars=%v", varNames), + ) +} + +func compileToolResponseFilterInternal[K comparable]( + cacheKey K, + filter string, + compileOpts []gojq.CompilerOption, + logFunctionName string, + logSuffix string, +) (*gojq.Code, error) { if cached, ok := filterCodeCache.Load(cacheKey); ok { code, ok := cached.(*gojq.Code) if !ok { // Should never happen; the cache only stores *gojq.Code values. return nil, fmt.Errorf("internal error: unexpected cached value type for filter (len=%d)", len(filter)) } - logMiddleware.Printf("CompileToolResponseFilterWithVars: cache hit, len=%d, vars=%v", len(filter), varNames) + logMiddleware.Printf("%s: cache hit, len=%d%s", logFunctionName, len(filter), logSuffix) return code, nil } - logMiddleware.Printf("CompileToolResponseFilterWithVars: parsing jq filter expression, len=%d, vars=%v", len(filter), varNames) + logMiddleware.Printf("%s: parsing jq filter expression, len=%d%s", logFunctionName, len(filter), logSuffix) query, err := gojq.Parse(filter) if err != nil { return nil, fmt.Errorf("failed to parse tool response filter: %w", err) } - code, err := gojq.Compile(query, - compileOptsWithVariables(varNames)..., - ) + code, err := gojq.Compile(query, compileOpts...) if err != nil { return nil, fmt.Errorf("failed to compile tool response filter: %w", err) } filterCodeCache.Store(cacheKey, code) - logMiddleware.Printf("CompileToolResponseFilterWithVars: filter compiled and cached successfully") + logMiddleware.Printf("%s: filter compiled and cached successfully", logFunctionName) return code, nil } From 7b72f300bee2ac0e70666982f307253dbbae46b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:38:27 +0000 Subject: [PATCH 3/3] test: cover separate jq cache paths for vars API --- internal/middleware/jqschema_coverage_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/internal/middleware/jqschema_coverage_test.go b/internal/middleware/jqschema_coverage_test.go index 5e7532218..fd1406070 100644 --- a/internal/middleware/jqschema_coverage_test.go +++ b/internal/middleware/jqschema_coverage_test.go @@ -552,6 +552,22 @@ func TestCompileToolResponseFilterWithVars_DifferentVarsCacheMiss(t *testing.T) assert.NotSame(t, code1, code2, "CompileToolResponseFilterWithVars should use distinct cache entries for different varNames") } +func TestCompileToolResponseFilterWithVars_DoesNotReusePlainFilterCache(t *testing.T) { + // Same filter text compiled through different APIs must not share cache entries, + // because plain and variable-enabled paths have different cache key types/options. + filter := ". | {ok: true}" + + plainCode, err := CompileToolResponseFilter(filter) + require.NoError(t, err) + require.NotNil(t, plainCode) + + varCode, err := CompileToolResponseFilterWithVars(filter, nil) + require.NoError(t, err) + require.NotNil(t, varCode) + + assert.NotSame(t, plainCode, varCode, "plain and var-enabled compilation should use distinct cache entries") +} + func TestToolResponseFilterVarsCacheKey_NoSeparatorCollision(t *testing.T) { t.Parallel()