Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 26 additions & 31 deletions internal/middleware/jqschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
)
Comment on lines +286 to +290
}

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
}

Expand Down
16 changes: 16 additions & 0 deletions internal/middleware/jqschema_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
Loading