(t *testing.T)
| 379 | } |
| 380 | |
| 381 | func TestInjectTools_CacheBreakpoints(t *testing.T) { |
| 382 | t.Parallel() |
| 383 | |
| 384 | t.Run("cache control preserved when no tools to inject", func(t *testing.T) { |
| 385 | t.Parallel() |
| 386 | |
| 387 | // Request has existing tool with cache control, but no tools to inject. |
| 388 | i := &interceptionBase{ |
| 389 | reqPayload: mustMessagesPayload(t, `{"tools":[`+ |
| 390 | `{"name":"existing_tool","type":"custom","input_schema":{"type":"object","properties":{}},"cache_control":{"type":"ephemeral"}}]}`), |
| 391 | mcpProxy: &mockServerProxier{tools: nil}, |
| 392 | logger: slog.Make(), |
| 393 | } |
| 394 | |
| 395 | i.injectTools() |
| 396 | |
| 397 | // Cache control should remain untouched since no tools were injected. |
| 398 | toolItems := gjson.GetBytes(i.reqPayload, "tools").Array() |
| 399 | require.Len(t, toolItems, 1) |
| 400 | require.Equal(t, "existing_tool", toolItems[0].Get("name").String()) |
| 401 | require.Equal(t, string(constant.ValueOf[constant.Ephemeral]()), toolItems[0].Get("cache_control.type").String()) |
| 402 | }) |
| 403 | |
| 404 | t.Run("cache control breakpoint is preserved by prepending injected tools", func(t *testing.T) { |
| 405 | t.Parallel() |
| 406 | |
| 407 | // Request has existing tool with cache control. |
| 408 | i := &interceptionBase{ |
| 409 | reqPayload: mustMessagesPayload(t, `{"tools":[`+ |
| 410 | `{"name":"existing_tool","type":"custom","input_schema":{"type":"object","properties":{}},"cache_control":{"type":"ephemeral"}}]}`), |
| 411 | mcpProxy: &mockServerProxier{ |
| 412 | tools: []*mcp.Tool{ |
| 413 | {ID: "injected_tool", Name: "injected", Description: "Injected tool"}, |
| 414 | }, |
| 415 | }, |
| 416 | logger: slog.Make(), |
| 417 | } |
| 418 | |
| 419 | i.injectTools() |
| 420 | |
| 421 | toolItems := gjson.GetBytes(i.reqPayload, "tools").Array() |
| 422 | require.Len(t, toolItems, 2) |
| 423 | // Injected tools are prepended. |
| 424 | require.Equal(t, "injected_tool", toolItems[0].Get("name").String()) |
| 425 | require.Empty(t, toolItems[0].Get("cache_control.type").String()) |
| 426 | // Original tool's cache control should be preserved at the end. |
| 427 | require.Equal(t, "existing_tool", toolItems[1].Get("name").String()) |
| 428 | require.Equal(t, string(constant.ValueOf[constant.Ephemeral]()), toolItems[1].Get("cache_control.type").String()) |
| 429 | }) |
| 430 | |
| 431 | // The cache breakpoint SHOULD be on the final tool, but may not be; we must preserve that intention. |
| 432 | t.Run("cache control breakpoint in non-standard location is preserved", func(t *testing.T) { |
| 433 | t.Parallel() |
| 434 | |
| 435 | // Request has multiple tools with cache control breakpoints. |
| 436 | i := &interceptionBase{ |
| 437 | reqPayload: mustMessagesPayload(t, `{"tools":[`+ |
| 438 | `{"name":"tool_with_cache_1","type":"custom","input_schema":{"type":"object","properties":{}},"cache_control":{"type":"ephemeral"}},`+ |
nothing calls this directly
no test coverage detected