From fca607177b10b80800619e27871120486678927e Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 13 Feb 2026 16:14:48 +0100 Subject: [PATCH 1/2] Remove fake models.dev Signed-off-by: David Gageot --- e2e/main_test.go | 20 -------------------- pkg/config/model_alias_test.go | 3 +++ pkg/modelsdev/store_test.go | 3 +++ pkg/teamloader/teamloader_test.go | 13 ------------- 4 files changed, 6 insertions(+), 33 deletions(-) delete mode 100644 e2e/main_test.go diff --git a/e2e/main_test.go b/e2e/main_test.go deleted file mode 100644 index 54c73e1a9..000000000 --- a/e2e/main_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package e2e_test - -import ( - "os" - "testing" - - "github.com/docker/cagent/pkg/modelsdev" -) - -// TestMain sets up the test environment for all e2e tests. -func TestMain(m *testing.M) { - store, err := modelsdev.NewStore() - if err != nil { - os.Exit(1) - } - store.SetDatabaseForTesting(&modelsdev.Database{ - Providers: make(map[string]modelsdev.Provider), - }) - os.Exit(m.Run()) -} diff --git a/pkg/config/model_alias_test.go b/pkg/config/model_alias_test.go index 4e790e9b0..944ff3810 100644 --- a/pkg/config/model_alias_test.go +++ b/pkg/config/model_alias_test.go @@ -30,6 +30,9 @@ func TestResolveModelAliases(t *testing.T) { store, err := modelsdev.NewStore(modelsdev.WithCacheDir(t.TempDir())) require.NoError(t, err) store.SetDatabaseForTesting(mockData) + t.Cleanup(func() { + store.SetDatabaseForTesting(nil) + }) ctx := t.Context() diff --git a/pkg/modelsdev/store_test.go b/pkg/modelsdev/store_test.go index ef422d3cd..9fbb46355 100644 --- a/pkg/modelsdev/store_test.go +++ b/pkg/modelsdev/store_test.go @@ -39,6 +39,9 @@ func TestResolveModelAlias(t *testing.T) { store, err := NewStore(WithCacheDir(t.TempDir())) require.NoError(t, err) store.SetDatabaseForTesting(mockData) + t.Cleanup(func() { + store.SetDatabaseForTesting(nil) + }) ctx := t.Context() diff --git a/pkg/teamloader/teamloader_test.go b/pkg/teamloader/teamloader_test.go index 87a37921d..cf30b65e7 100644 --- a/pkg/teamloader/teamloader_test.go +++ b/pkg/teamloader/teamloader_test.go @@ -17,22 +17,9 @@ import ( "github.com/docker/cagent/pkg/config/latest" "github.com/docker/cagent/pkg/environment" "github.com/docker/cagent/pkg/model/provider/dmr" - "github.com/docker/cagent/pkg/modelsdev" "github.com/docker/cagent/pkg/tools" ) -// TestMain sets up the test environment for all tests in this package. -func TestMain(m *testing.M) { - store, err := modelsdev.NewStore() - if err != nil { - os.Exit(1) - } - store.SetDatabaseForTesting(&modelsdev.Database{ - Providers: make(map[string]modelsdev.Provider), - }) - os.Exit(m.Run()) -} - // skipExamples contains example files that require cloud-specific configurations // (e.g., AWS profiles, GCP credentials) that can't be mocked with dummy env vars. var skipExamples = map[string]string{ From 0a5ec80b44131af65cb0acfacb862efa7a6c8965 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 12 Feb 2026 15:28:43 +0100 Subject: [PATCH 2/2] fix: recursively enforce required properties in OpenAI tool schemas Fixes Chrome MCP with openai 5.2 Signed-off-by: David Gageot --- e2e/cagent_exec_test.go | 4 +- .../cassettes/TestExec_Mistral_ToolCall.yaml | 18 +-- .../TestExec_OpenAI_HideToolCalls.yaml | 40 +++---- .../cassettes/TestExec_OpenAI_ToolCall.yaml | 40 +++---- pkg/model/provider/openai/schema.go | 104 +++++++++------- pkg/model/provider/openai/schema_test.go | 112 ++++++++++++++++++ 6 files changed, 220 insertions(+), 98 deletions(-) diff --git a/e2e/cagent_exec_test.go b/e2e/cagent_exec_test.go index b93014e05..14d4f9d98 100644 --- a/e2e/cagent_exec_test.go +++ b/e2e/cagent_exec_test.go @@ -122,9 +122,7 @@ func TestExec_Mistral(t *testing.T) { func TestExec_Mistral_ToolCall(t *testing.T) { out := cagent(t, "exec", "testdata/fs_tools.yaml", "--model=mistral/mistral-small", "How many files in testdata/working_dir? Only output the number.") - // NOTE: If you look at the LLM response, Mistral says it sees 2 files, yours truly got tired of re-running this test to get it to say "1". - // For now, just update the expected output - require.Equal(t, "\n--- Agent: root ---\n\nCalling list_directory(path: \"testdata/working_dir\")\n\nlist_directory response → \"FILE README.me\\n\"\n2", out) + require.Equal(t, "\n--- Agent: root ---\n\nCalling list_directory(path: \"testdata/working_dir\")\n\nlist_directory response → \"FILE README.me\\n\"\n1", out) } func TestExec_ToolCallsNeedAcceptance(t *testing.T) { diff --git a/e2e/testdata/cassettes/TestExec_Mistral_ToolCall.yaml b/e2e/testdata/cassettes/TestExec_Mistral_ToolCall.yaml index a42b3e784..7cdec25e1 100644 --- a/e2e/testdata/cassettes/TestExec_Mistral_ToolCall.yaml +++ b/e2e/testdata/cassettes/TestExec_Mistral_ToolCall.yaml @@ -8,7 +8,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.mistral.ai - body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"}],"model":"mistral-small","stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' + body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"}],"model":"mistral-small","max_tokens":32000,"stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.mistral.ai/v1/chat/completions method: POST response: @@ -17,16 +17,16 @@ interactions: proto_minor: 0 content_length: -1 body: |+ - data: {"id":"0b26e2d4c5d440ff90d0ca631bd787f6","object":"chat.completion.chunk","created":1768577346,"model":"mistral-small","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} + data: {"id":"3cd98f3d5c5f48ebad48fe703ffe77fe","object":"chat.completion.chunk","created":1770996032,"model":"mistral-small","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} - data: {"id":"0b26e2d4c5d440ff90d0ca631bd787f6","object":"chat.completion.chunk","created":1768577346,"model":"mistral-small","choices":[{"index":0,"delta":{"tool_calls":[{"id":"UoBTx8yck","function":{"name":"list_directory","arguments":"{\"path\": \"testdata/working_dir\"}"},"index":0}]},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":1008,"total_tokens":1023,"completion_tokens":15},"p":"abcdefghijklmn"} + data: {"id":"3cd98f3d5c5f48ebad48fe703ffe77fe","object":"chat.completion.chunk","created":1770996032,"model":"mistral-small","choices":[{"index":0,"delta":{"tool_calls":[{"id":"j5EBzKUla","function":{"name":"list_directory","arguments":"{\"path\": \"testdata/working_dir\"}"},"index":0}]},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":1008,"total_tokens":1023,"completion_tokens":15,"prompt_tokens_details":{"cached_tokens":0}},"p":"abcdefghijklmnopqrstuvwxyz0123456"} data: [DONE] headers: {} status: 200 OK code: 200 - duration: 417.236792ms + duration: 447.407458ms - id: 1 request: proto: HTTP/1.1 @@ -34,7 +34,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.mistral.ai - body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"UoBTx8yck","function":{"arguments":"{\"path\": \"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"UoBTx8yck","role":"tool"}],"model":"mistral-small","stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' + body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"j5EBzKUla","function":{"arguments":"{\"path\": \"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"j5EBzKUla","role":"tool"}],"model":"mistral-small","max_tokens":32000,"stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.mistral.ai/v1/chat/completions method: POST response: @@ -43,15 +43,15 @@ interactions: proto_minor: 0 content_length: -1 body: |+ - data: {"id":"d76fad82570b48eb81b69a81f7e92864","object":"chat.completion.chunk","created":1768577347,"model":"mistral-small","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} + data: {"id":"a4d2c3b1c28644b492fc253f8af4b77c","object":"chat.completion.chunk","created":1770996032,"model":"mistral-small","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} - data: {"id":"d76fad82570b48eb81b69a81f7e92864","object":"chat.completion.chunk","created":1768577347,"model":"mistral-small","choices":[{"index":0,"delta":{"content":"2"},"finish_reason":null}],"p":"abcdefghij"} + data: {"id":"a4d2c3b1c28644b492fc253f8af4b77c","object":"chat.completion.chunk","created":1770996032,"model":"mistral-small","choices":[{"index":0,"delta":{"content":"1"},"finish_reason":null}],"p":"abcd"} - data: {"id":"d76fad82570b48eb81b69a81f7e92864","object":"chat.completion.chunk","created":1768577347,"model":"mistral-small","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop"}],"usage":{"prompt_tokens":1045,"total_tokens":1047,"completion_tokens":2},"p":"abcdefghijklm"} + data: {"id":"a4d2c3b1c28644b492fc253f8af4b77c","object":"chat.completion.chunk","created":1770996032,"model":"mistral-small","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop"}],"usage":{"prompt_tokens":1045,"total_tokens":1047,"completion_tokens":2,"prompt_tokens_details":{"cached_tokens":0}},"p":"abcdefghijklmnopqrstuvwxyz012345678"} data: [DONE] headers: {} status: 200 OK code: 200 - duration: 193.7175ms + duration: 244.350583ms diff --git a/e2e/testdata/cassettes/TestExec_OpenAI_HideToolCalls.yaml b/e2e/testdata/cassettes/TestExec_OpenAI_HideToolCalls.yaml index 2d95ef109..c08cd2d8f 100644 --- a/e2e/testdata/cassettes/TestExec_OpenAI_HideToolCalls.yaml +++ b/e2e/testdata/cassettes/TestExec_OpenAI_HideToolCalls.yaml @@ -8,7 +8,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.openai.com - body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"}],"model":"gpt-4o","stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' + body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"}],"model":"gpt-4o","max_tokens":16384,"stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.openai.com/v1/chat/completions method: POST response: @@ -17,36 +17,36 @@ interactions: proto_minor: 0 content_length: -1 body: |+ - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_HhDNaMq27t33NzVKCg4jSTIj","type":"function","function":{"name":"list_directory","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iSajem"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_45PoYwbnIoUmX0W2kXCQqm9W","type":"function","function":{"name":"list_directory","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"HL2eEa"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SRT"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Juz"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"path"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"qt"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"path"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"EH"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"d"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"a"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"test"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"yL"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"test"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"t7"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"data"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"HO"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"data"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Kg"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"/"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xe7aF"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"/"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vDLyl"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"working"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"AeLjzLZgsQzsQSi"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"working"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZgLOv9g1bNndkH0"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_dir"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"qg"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_dir"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vy"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"dqZ"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"HCM"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"YnAH"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"ba8F"} - data: {"id":"chatcmpl-CyfunvtCL223un1Lb9ZjxLJzc83Pp","object":"chat.completion.chunk","created":1768576837,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[],"usage":{"prompt_tokens":656,"completion_tokens":18,"total_tokens":674,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"aVMqhJo3ND4XT"} + data: {"id":"chatcmpl-D8pFsmrBRzmoPNjwamdE0NavgIf99","object":"chat.completion.chunk","created":1770996020,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[],"usage":{"prompt_tokens":656,"completion_tokens":18,"total_tokens":674,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"YFbzavNqvY3KS"} data: [DONE] headers: {} status: 200 OK code: 200 - duration: 1.09946225s + duration: 681.77925ms - id: 1 request: proto: HTTP/1.1 @@ -54,7 +54,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.openai.com - body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"call_HhDNaMq27t33NzVKCg4jSTIj","function":{"arguments":"{\"path\":\"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"call_HhDNaMq27t33NzVKCg4jSTIj","role":"tool"}],"model":"gpt-4o","stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' + body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"call_45PoYwbnIoUmX0W2kXCQqm9W","function":{"arguments":"{\"path\":\"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"call_45PoYwbnIoUmX0W2kXCQqm9W","role":"tool"}],"model":"gpt-4o","max_tokens":16384,"stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.openai.com/v1/chat/completions method: POST response: @@ -63,17 +63,17 @@ interactions: proto_minor: 0 content_length: -1 body: |+ - data: {"id":"chatcmpl-Cyfuos3fSKlyz5qXYwxlFyjIG4Res","object":"chat.completion.chunk","created":1768576838,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"XTiQu0jVD5eYSk"} + data: {"id":"chatcmpl-D8pFt0tWnBk9BaNRplerHJ0KdYN8d","object":"chat.completion.chunk","created":1770996021,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Vn1hlPMHz0tPnV"} - data: {"id":"chatcmpl-Cyfuos3fSKlyz5qXYwxlFyjIG4Res","object":"chat.completion.chunk","created":1768576838,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":"1"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"tbZGIFMnW18gehc"} + data: {"id":"chatcmpl-D8pFt0tWnBk9BaNRplerHJ0KdYN8d","object":"chat.completion.chunk","created":1770996021,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{"content":"1"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"qd9Ih0YatpjWJgQ"} - data: {"id":"chatcmpl-Cyfuos3fSKlyz5qXYwxlFyjIG4Res","object":"chat.completion.chunk","created":1768576838,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"FISOzNkJgo"} + data: {"id":"chatcmpl-D8pFt0tWnBk9BaNRplerHJ0KdYN8d","object":"chat.completion.chunk","created":1770996021,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"Rhkg3myiQQ"} - data: {"id":"chatcmpl-Cyfuos3fSKlyz5qXYwxlFyjIG4Res","object":"chat.completion.chunk","created":1768576838,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[],"usage":{"prompt_tokens":686,"completion_tokens":2,"total_tokens":688,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"omjF9HFRAa4nWK"} + data: {"id":"chatcmpl-D8pFt0tWnBk9BaNRplerHJ0KdYN8d","object":"chat.completion.chunk","created":1770996021,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_ad98c18a04","choices":[],"usage":{"prompt_tokens":686,"completion_tokens":2,"total_tokens":688,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"t2KiIbtSOjrOVj"} data: [DONE] headers: {} status: 200 OK code: 200 - duration: 466.217458ms + duration: 428.712708ms diff --git a/e2e/testdata/cassettes/TestExec_OpenAI_ToolCall.yaml b/e2e/testdata/cassettes/TestExec_OpenAI_ToolCall.yaml index 842ffe6cd..92d5a6f68 100644 --- a/e2e/testdata/cassettes/TestExec_OpenAI_ToolCall.yaml +++ b/e2e/testdata/cassettes/TestExec_OpenAI_ToolCall.yaml @@ -8,7 +8,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.openai.com - body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"}],"model":"gpt-4o","stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' + body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"}],"model":"gpt-4o","max_tokens":16384,"stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.openai.com/v1/chat/completions method: POST response: @@ -17,36 +17,36 @@ interactions: proto_minor: 0 content_length: -1 body: |+ - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_xnKIXWsStUYaLFCIEzmSuUgq","type":"function","function":{"name":"list_directory","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"H8abVJ"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_b1xfQl8yj71KXoQm5v0BKn0f","type":"function","function":{"name":"list_directory","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1k7RVe"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"B6B"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"oWi"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"path"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Z2"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"path"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"HD"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"9"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"3"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"test"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Ij"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"test"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SW"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"data"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"WI"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"data"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Uw"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"/"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zvCv8"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"/"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"4lG94"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"working"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1E9U6xH0KcHt1T7"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"working"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"jtkFjef5nvUv0rY"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_dir"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KB"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_dir"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"rf"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"wvs"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"sfm"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"OAso"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"zKGT"} - data: {"id":"chatcmpl-CyfukXgh3vHxh4C4MPS3qUw0m58vw","object":"chat.completion.chunk","created":1768576834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[],"usage":{"prompt_tokens":656,"completion_tokens":18,"total_tokens":674,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"nwHtuivBKJj9m"} + data: {"id":"chatcmpl-D8pFSp0PTZRurIl1YK5uSIQpaDYR8","object":"chat.completion.chunk","created":1770995994,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[],"usage":{"prompt_tokens":656,"completion_tokens":18,"total_tokens":674,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"UgaXFljcFpu16"} data: [DONE] headers: {} status: 200 OK code: 200 - duration: 990.110167ms + duration: 1.059753583s - id: 1 request: proto: HTTP/1.1 @@ -54,7 +54,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.openai.com - body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"call_xnKIXWsStUYaLFCIEzmSuUgq","function":{"arguments":"{\"path\":\"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"call_xnKIXWsStUYaLFCIEzmSuUgq","role":"tool"}],"model":"gpt-4o","stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' + body: '{"messages":[{"content":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n","role":"system"},{"content":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"call_b1xfQl8yj71KXoQm5v0BKn0f","function":{"arguments":"{\"path\":\"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"call_b1xfQl8yj71KXoQm5v0BKn0f","role":"tool"}],"model":"gpt-4o","max_tokens":16384,"stream_options":{"include_usage":true},"tools":[{"function":{"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to traverse (relative to working directory)","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","parameters":{"additionalProperties":false,"properties":{"edits":{"description":"Array of edit operations","items":{"additionalProperties":false,"properties":{"newText":{"description":"The replacement text","type":"string"},"oldText":{"description":"The exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":["null","array"]},"path":{"description":"The file path to edit","type":"string"}},"required":["edits","path"],"type":"object"}},"type":"function"},{"function":{"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The directory path to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the complete contents of a file from the file system.","parameters":{"additionalProperties":false,"properties":{"path":{"description":"The file path to read","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Whether to return the result as JSON","type":["boolean","null"]},"paths":{"description":"Array of file paths to read","items":{"type":"string"},"type":["null","array"]}},"required":["json","paths"],"type":"object"}},"type":"function"},{"function":{"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","parameters":{"additionalProperties":false,"properties":{"excludePatterns":{"description":"Patterns to exclude from search","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"If true, treat query as regex; otherwise literal text","type":["boolean","null"]},"path":{"description":"The starting directory path","type":"string"},"query":{"description":"The text or regex pattern to search for","type":"string"}},"required":["excludePatterns","is_regex","path","query"],"type":"object"}},"type":"function"},{"function":{"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.openai.com/v1/chat/completions method: POST response: @@ -63,17 +63,17 @@ interactions: proto_minor: 0 content_length: -1 body: |+ - data: {"id":"chatcmpl-Cyfum1EnlgipfRRbEVf7ppDbV7QIm","object":"chat.completion.chunk","created":1768576836,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0jbpi3ielAkp9i"} + data: {"id":"chatcmpl-D8pFTh8xolpWY1i8oJKZMoxOtGspI","object":"chat.completion.chunk","created":1770995995,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KDlKAVp92GYPLd"} - data: {"id":"chatcmpl-Cyfum1EnlgipfRRbEVf7ppDbV7QIm","object":"chat.completion.chunk","created":1768576836,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":"1"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2hBlncwIRsZWPTd"} + data: {"id":"chatcmpl-D8pFTh8xolpWY1i8oJKZMoxOtGspI","object":"chat.completion.chunk","created":1770995995,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{"content":"1"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1pZAb9PYI4wDyI9"} - data: {"id":"chatcmpl-Cyfum1EnlgipfRRbEVf7ppDbV7QIm","object":"chat.completion.chunk","created":1768576836,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"rcQzOCy2V0"} + data: {"id":"chatcmpl-D8pFTh8xolpWY1i8oJKZMoxOtGspI","object":"chat.completion.chunk","created":1770995995,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"VytIgX7pnA"} - data: {"id":"chatcmpl-Cyfum1EnlgipfRRbEVf7ppDbV7QIm","object":"chat.completion.chunk","created":1768576836,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[],"usage":{"prompt_tokens":686,"completion_tokens":2,"total_tokens":688,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"UnJJBPc4SNmrKG"} + data: {"id":"chatcmpl-D8pFTh8xolpWY1i8oJKZMoxOtGspI","object":"chat.completion.chunk","created":1770995995,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_64dfa806c7","choices":[],"usage":{"prompt_tokens":686,"completion_tokens":2,"total_tokens":688,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"H29MEt5yOj0ujz"} data: [DONE] headers: {} status: 200 OK code: 200 - duration: 916.738083ms + duration: 670.828083ms diff --git a/pkg/model/provider/openai/schema.go b/pkg/model/provider/openai/schema.go index eeddfc7fd..9934096c6 100644 --- a/pkg/model/provider/openai/schema.go +++ b/pkg/model/provider/openai/schema.go @@ -19,78 +19,90 @@ func ConvertParametersToSchema(params any) (shared.FunctionParameters, error) { return fixSchemaArrayItems(removeFormatFields(makeAllRequired(p))), nil } -// makeAllRequired make all the parameters "required" -// because that's what the Response API wants, now. -func makeAllRequired(schema shared.FunctionParameters) shared.FunctionParameters { - if schema == nil { - return makeAllRequired(map[string]any{"type": "object", "properties": map[string]any{}}) - } +// walkSchema calls fn on the given schema node, then recursively walks into +// properties, anyOf/oneOf/allOf variants, and array items. +func walkSchema(schema map[string]any, fn func(map[string]any)) { + fn(schema) - properties, ok := schema["properties"].(map[string]any) - if !ok { - return schema + if properties, ok := schema["properties"].(map[string]any); ok { + for _, v := range properties { + if sub, ok := v.(map[string]any); ok { + walkSchema(sub, fn) + } + } } - reallyRequired := map[string]bool{} - if required, ok := schema["required"].([]any); ok { - for _, name := range required { - reallyRequired[name.(string)] = true + for _, keyword := range []string{"anyOf", "oneOf", "allOf"} { + if variants, ok := schema[keyword].([]any); ok { + for _, v := range variants { + if sub, ok := v.(map[string]any); ok { + walkSchema(sub, fn) + } + } } } - // We can't use a nil 'required' attribute - newRequired := []any{} + if items, ok := schema["items"].(map[string]any); ok { + walkSchema(items, fn) + } +} - // Sort property names for deterministic output - propNames := slices.Sorted(maps.Keys(properties)) +// makeAllRequired makes all object properties "required" throughout the schema, +// because that's what the OpenAI Response API demands. +// Properties that were not originally required are made nullable. +func makeAllRequired(schema shared.FunctionParameters) shared.FunctionParameters { + if schema == nil { + schema = map[string]any{"type": "object", "properties": map[string]any{}} + } - for _, propName := range propNames { - newRequired = append(newRequired, propName) - if reallyRequired[propName] { - continue + walkSchema(schema, func(node map[string]any) { + properties, ok := node["properties"].(map[string]any) + if !ok { + return } - // Make its type nullable - if propMap, ok := properties[propName].(map[string]any); ok { - if typeValue, ok := propMap["type"].(string); ok { - propMap["type"] = []string{typeValue, "null"} + originallyRequired := map[string]bool{} + if required, ok := node["required"].([]any); ok { + for _, name := range required { + originallyRequired[name.(string)] = true } } - } - schema["required"] = newRequired - schema["additionalProperties"] = false + newRequired := []any{} + for _, propName := range slices.Sorted(maps.Keys(properties)) { + newRequired = append(newRequired, propName) + + // Make newly-required properties nullable + if !originallyRequired[propName] { + if propMap, ok := properties[propName].(map[string]any); ok { + if t, ok := propMap["type"].(string); ok { + propMap["type"] = []string{t, "null"} + } + } + } + } + + node["required"] = newRequired + node["additionalProperties"] = false + }) + return schema } -// removeFormatFields removes the "format" field from all properties in the schema, recursively. +// removeFormatFields removes the "format" field from all nodes in the schema. // OpenAI does not support the JSON Schema "format" keyword (e.g. "uri", "email", "date"). func removeFormatFields(schema shared.FunctionParameters) shared.FunctionParameters { if schema == nil { return nil } - removeFormatFieldsRecursive(schema) + walkSchema(schema, func(node map[string]any) { + delete(node, "format") + }) return schema } -func removeFormatFieldsRecursive(schema map[string]any) { - delete(schema, "format") - - if properties, ok := schema["properties"].(map[string]any); ok { - for _, propValue := range properties { - if prop, ok := propValue.(map[string]any); ok { - removeFormatFieldsRecursive(prop) - } - } - } - - if items, ok := schema["items"].(map[string]any); ok { - removeFormatFieldsRecursive(items) - } -} - // In Docker Desktop 4.52, the MCP Gateway produces an invalid tools shema for `mcp-config-set`. func fixSchemaArrayItems(schema shared.FunctionParameters) shared.FunctionParameters { propertiesValue, ok := schema["properties"] diff --git a/pkg/model/provider/openai/schema_test.go b/pkg/model/provider/openai/schema_test.go index 266cfcf98..5f26cd0dc 100644 --- a/pkg/model/provider/openai/schema_test.go +++ b/pkg/model/provider/openai/schema_test.go @@ -55,6 +55,118 @@ func TestMakeAllRequired_NilSchema(t *testing.T) { assert.JSONEq(t, `{"additionalProperties":false,"properties":{},"type":"object","required":[]}`, string(buf)) } +func TestMakeAllRequired_AnyOf(t *testing.T) { + // Reproduces the chrome-devtools-mcp "emulate" tool schema where + // viewport has an anyOf with an object variant whose properties + // are not all listed in required. OpenAI rejects this. + schema := shared.FunctionParameters{ + "type": "object", + "properties": map[string]any{ + "viewport": map[string]any{ + "anyOf": []any{ + map[string]any{ + "type": "object", + "properties": map[string]any{ + "width": map[string]any{"type": "number"}, + "height": map[string]any{"type": "number"}, + "deviceScaleFactor": map[string]any{"type": "number"}, + }, + "required": []any{"width", "height"}, + }, + map[string]any{ + "type": "null", + }, + }, + }, + }, + "required": []any{"viewport"}, + } + + updated := makeAllRequired(schema) + + // Top-level: viewport must be required + required := updated["required"].([]any) + assert.Contains(t, required, "viewport") + + // anyOf[0]: all properties must be required, including deviceScaleFactor + viewport := updated["properties"].(map[string]any)["viewport"].(map[string]any) + anyOf := viewport["anyOf"].([]any) + variant := anyOf[0].(map[string]any) + variantRequired := variant["required"].([]any) + assert.Len(t, variantRequired, 3) + assert.Contains(t, variantRequired, "width") + assert.Contains(t, variantRequired, "height") + assert.Contains(t, variantRequired, "deviceScaleFactor") + + // deviceScaleFactor was not originally required, so its type should be nullable + dsf := variant["properties"].(map[string]any)["deviceScaleFactor"].(map[string]any) + assert.Equal(t, []string{"number", "null"}, dsf["type"]) + + // width was originally required, so its type should be unchanged + w := variant["properties"].(map[string]any)["width"].(map[string]any) + assert.Equal(t, "number", w["type"]) +} + +func TestMakeAllRequired_NestedProperties(t *testing.T) { + schema := shared.FunctionParameters{ + "type": "object", + "properties": map[string]any{ + "config": map[string]any{ + "type": "object", + "properties": map[string]any{ + "name": map[string]any{"type": "string"}, + "value": map[string]any{"type": "string"}, + }, + "required": []any{"name"}, + }, + }, + "required": []any{"config"}, + } + + updated := makeAllRequired(schema) + + // Nested object: all properties must be required + config := updated["properties"].(map[string]any)["config"].(map[string]any) + configRequired := config["required"].([]any) + assert.Len(t, configRequired, 2) + assert.Contains(t, configRequired, "name") + assert.Contains(t, configRequired, "value") + + // value was not originally required, so its type should be nullable + value := config["properties"].(map[string]any)["value"].(map[string]any) + assert.Equal(t, []string{"string", "null"}, value["type"]) +} + +func TestMakeAllRequired_ArrayItems(t *testing.T) { + schema := shared.FunctionParameters{ + "type": "object", + "properties": map[string]any{ + "items": map[string]any{ + "type": "array", + "items": map[string]any{ + "type": "object", + "properties": map[string]any{ + "id": map[string]any{"type": "string"}, + "name": map[string]any{"type": "string"}, + }, + "required": []any{"id"}, + }, + }, + }, + "required": []any{"items"}, + } + + updated := makeAllRequired(schema) + + // Array items object: all properties must be required + itemsSchema := updated["properties"].(map[string]any)["items"].(map[string]any) + itemObj := itemsSchema["items"].(map[string]any) + itemRequired := itemObj["required"].([]any) + assert.Len(t, itemRequired, 2) + assert.Contains(t, itemRequired, "id") + assert.Contains(t, itemRequired, "name") +} + func TestRemoveFormatFields(t *testing.T) { schema := map[string]any{ "type": "object",