From 82a665b04d64622be351e4f87c35e846bbe8cb91 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 10 Sep 2026 15:24:07 +0200 Subject: [PATCH 1/2] Revert "feat(filesystem): fail fast on foreign-OS paths and explain path resolution" This reverts commit cec4ba4923a8a07393e7730392982c86ab159218. Assisted-By: Claude --- .../TestExec_Anthropic_ToolCall.yaml | 4 +- .../cassettes/TestExec_Gemini_ToolCall.yaml | 4 +- .../cassettes/TestExec_Mistral_ToolCall.yaml | 4 +- .../TestExec_OpenAI_HideToolCalls.yaml | 4 +- .../cassettes/TestExec_OpenAI_ToolCall.yaml | 4 +- .../TestExec_ToolCallsNeedAcceptance.yaml | 4 +- pkg/fake/proxy.go | 5 -- pkg/tools/builtin/filesystem/filesystem.go | 73 ++----------------- .../filesystem/filesystem_paths_test.go | 62 +--------------- .../builtin/filesystem/filesystem_test.go | 12 +-- 10 files changed, 24 insertions(+), 152 deletions(-) diff --git a/e2e/testdata/cassettes/TestExec_Anthropic_ToolCall.yaml b/e2e/testdata/cassettes/TestExec_Anthropic_ToolCall.yaml index b16bf9704b..99a67bf1fb 100644 --- a/e2e/testdata/cassettes/TestExec_Anthropic_ToolCall.yaml +++ b/e2e/testdata/cassettes/TestExec_Anthropic_ToolCall.yaml @@ -8,7 +8,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.anthropic.com - body: '{"max_tokens":64000,"messages":[{"content":[{"text":"How many files in testdata/working_dir? Only output the number.","cache_control":{"type":"ephemeral"},"type":"text"}],"role":"user"}],"model":"claude-sonnet-4-0","system":[{"text":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.","type":"text"},{"text":"## Filesystem Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","cache_control":{"type":"ephemeral"},"type":"text"}],"tools":[{"input_schema":{"properties":{"path":{"description":"Directory to traverse","type":"string"}},"required":["path"],"type":"object"},"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure."},{"input_schema":{"properties":{"edits":{"description":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":"array"},"path":{"description":"File to edit","type":"string"}},"required":["path","edits"],"type":"object"},"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content."},{"input_schema":{"properties":{"path":{"description":"Directory to list","type":"string"}},"required":["path"],"type":"object"},"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path."},{"input_schema":{"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["path"],"type":"object"},"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly."},{"input_schema":{"properties":{"json":{"description":"Return result as JSON","type":"boolean"},"paths":{"description":"Files to read","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously."},{"input_schema":{"properties":{"excludePatterns":{"description":"Patterns to exclude","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":"boolean"},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","type":"string"}},"required":["path","query"],"type":"object"},"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern."},{"input_schema":{"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["path","content"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content."},{"input_schema":{"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"create_directory","description":"Create one or more new directories or nested directory structures."},{"input_schema":{"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"remove_directory","description":"Remove one or more empty directories."}],"stream":true}' + body: '{"max_tokens":64000,"messages":[{"content":[{"text":"How many files in testdata/working_dir? Only output the number.","cache_control":{"type":"ephemeral"},"type":"text"}],"role":"user"}],"model":"claude-sonnet-4-0","system":[{"text":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.","type":"text"},{"text":"## Filesystem Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","cache_control":{"type":"ephemeral"},"type":"text"}],"tools":[{"input_schema":{"properties":{"path":{"description":"Directory to traverse","type":"string"}},"required":["path"],"type":"object"},"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure."},{"input_schema":{"properties":{"edits":{"description":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":"array"},"path":{"description":"File to edit","type":"string"}},"required":["path","edits"],"type":"object"},"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content."},{"input_schema":{"properties":{"path":{"description":"Directory to list","type":"string"}},"required":["path"],"type":"object"},"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path."},{"input_schema":{"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["path"],"type":"object"},"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly."},{"input_schema":{"properties":{"json":{"description":"Return result as JSON","type":"boolean"},"paths":{"description":"Files to read","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously."},{"input_schema":{"properties":{"excludePatterns":{"description":"Patterns to exclude","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":"boolean"},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","type":"string"}},"required":["path","query"],"type":"object"},"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern."},{"input_schema":{"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["path","content"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content."},{"input_schema":{"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"create_directory","description":"Create one or more new directories or nested directory structures."},{"input_schema":{"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"remove_directory","description":"Remove one or more empty directories."}],"stream":true}' url: https://api.anthropic.com/v1/messages method: POST response: @@ -55,7 +55,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.anthropic.com - body: '{"max_tokens":64000,"messages":[{"content":[{"text":"How many files in testdata/working_dir? Only output the number.","type":"text"}],"role":"user"},{"content":[{"id":"toolu_012gmfqnoTX8c5aV3vMWUnas","input":{"path":"testdata/working_dir"},"name":"list_directory","cache_control":{"type":"ephemeral"},"type":"tool_use"}],"role":"assistant"},{"content":[{"tool_use_id":"toolu_012gmfqnoTX8c5aV3vMWUnas","is_error":false,"cache_control":{"type":"ephemeral"},"content":[{"text":"FILE README.me\n","type":"text"}],"type":"tool_result"}],"role":"user"}],"model":"claude-sonnet-4-0","system":[{"text":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.","type":"text"},{"text":"## Filesystem Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","cache_control":{"type":"ephemeral"},"type":"text"}],"tools":[{"input_schema":{"properties":{"path":{"description":"Directory to traverse","type":"string"}},"required":["path"],"type":"object"},"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure."},{"input_schema":{"properties":{"edits":{"description":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":"array"},"path":{"description":"File to edit","type":"string"}},"required":["path","edits"],"type":"object"},"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content."},{"input_schema":{"properties":{"path":{"description":"Directory to list","type":"string"}},"required":["path"],"type":"object"},"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path."},{"input_schema":{"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["path"],"type":"object"},"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly."},{"input_schema":{"properties":{"json":{"description":"Return result as JSON","type":"boolean"},"paths":{"description":"Files to read","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously."},{"input_schema":{"properties":{"excludePatterns":{"description":"Patterns to exclude","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":"boolean"},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","type":"string"}},"required":["path","query"],"type":"object"},"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern."},{"input_schema":{"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["path","content"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content."},{"input_schema":{"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"create_directory","description":"Create one or more new directories or nested directory structures."},{"input_schema":{"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"remove_directory","description":"Remove one or more empty directories."}],"stream":true}' + body: '{"max_tokens":64000,"messages":[{"content":[{"text":"How many files in testdata/working_dir? Only output the number.","type":"text"}],"role":"user"},{"content":[{"id":"toolu_012gmfqnoTX8c5aV3vMWUnas","input":{"path":"testdata/working_dir"},"name":"list_directory","cache_control":{"type":"ephemeral"},"type":"tool_use"}],"role":"assistant"},{"content":[{"tool_use_id":"toolu_012gmfqnoTX8c5aV3vMWUnas","is_error":false,"cache_control":{"type":"ephemeral"},"content":[{"text":"FILE README.me\n","type":"text"}],"type":"tool_result"}],"role":"user"}],"model":"claude-sonnet-4-0","system":[{"text":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.","type":"text"},{"text":"## Filesystem Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","cache_control":{"type":"ephemeral"},"type":"text"}],"tools":[{"input_schema":{"properties":{"path":{"description":"Directory to traverse","type":"string"}},"required":["path"],"type":"object"},"name":"directory_tree","description":"Get a recursive tree view of files and directories as a JSON structure."},{"input_schema":{"properties":{"edits":{"description":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":"array"},"path":{"description":"File to edit","type":"string"}},"required":["path","edits"],"type":"object"},"name":"edit_file","description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content."},{"input_schema":{"properties":{"path":{"description":"Directory to list","type":"string"}},"required":["path"],"type":"object"},"name":"list_directory","description":"Get a detailed listing of all files and directories in a specified path."},{"input_schema":{"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["path"],"type":"object"},"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly."},{"input_schema":{"properties":{"json":{"description":"Return result as JSON","type":"boolean"},"paths":{"description":"Files to read","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously."},{"input_schema":{"properties":{"excludePatterns":{"description":"Patterns to exclude","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":"boolean"},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","type":"string"}},"required":["path","query"],"type":"object"},"name":"search_files_content","description":"Searches for text or regex patterns in the content of files matching a GLOB pattern."},{"input_schema":{"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["path","content"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content."},{"input_schema":{"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"create_directory","description":"Create one or more new directories or nested directory structures."},{"input_schema":{"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"},"name":"remove_directory","description":"Remove one or more empty directories."}],"stream":true}' url: https://api.anthropic.com/v1/messages method: POST response: diff --git a/e2e/testdata/cassettes/TestExec_Gemini_ToolCall.yaml b/e2e/testdata/cassettes/TestExec_Gemini_ToolCall.yaml index 6588b8dcb4..b955ef09b4 100644 --- a/e2e/testdata/cassettes/TestExec_Gemini_ToolCall.yaml +++ b/e2e/testdata/cassettes/TestExec_Gemini_ToolCall.yaml @@ -9,7 +9,7 @@ interactions: content_length: 0 host: generativelanguage.googleapis.com body: | - {"contents":[{"parts":[{"text":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n"}],"role":"user"},{"parts":[{"text":"## Filesystem Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output"}],"role":"user"},{"parts":[{"text":"How many files in testdata/working_dir? Only output the number."}],"role":"user"}],"generationConfig":{"maxOutputTokens":65536,"thinkingConfig":{"thinkingBudget":0}},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get a recursive tree view of files and directories as a JSON structure.","name":"directory_tree","parameters":{"properties":{"path":{"description":"Directory to traverse","type":"string"}},"required":["path"],"type":"object"}},{"description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","name":"edit_file","parameters":{"properties":{"edits":{"description":"Edits to apply","items":{"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":"array"},"path":{"description":"File to edit","type":"string"}},"required":["path","edits"],"type":"object"}},{"description":"Get a detailed listing of all files and directories in a specified path.","name":"list_directory","parameters":{"properties":{"path":{"description":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},{"description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","name":"read_file","parameters":{"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":"integer"},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":"integer"},"path":{"description":"File to read","type":"string"}},"required":["path"],"type":"object"}},{"description":"Read the contents of multiple files simultaneously.","name":"read_multiple_files","parameters":{"properties":{"json":{"description":"Return result as JSON","type":"boolean"},"paths":{"description":"Files to read","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},{"description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","name":"search_files_content","parameters":{"properties":{"excludePatterns":{"description":"Patterns to exclude","items":{"type":"string"},"type":"array"},"is_regex":{"description":"Treat query as regex","type":"boolean"},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","type":"string"}},"required":["path","query"],"type":"object"}},{"description":"Create a new file or completely overwrite an existing file with new content.","name":"write_file","parameters":{"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["path","content"],"type":"object"}},{"description":"Create one or more new directories or nested directory structures.","name":"create_directory","parameters":{"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},{"description":"Remove one or more empty directories.","name":"remove_directory","parameters":{"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}}]}]} + {"contents":[{"parts":[{"text":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n"}],"role":"user"},{"parts":[{"text":"## Filesystem Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output"}],"role":"user"},{"parts":[{"text":"How many files in testdata/working_dir? Only output the number."}],"role":"user"}],"generationConfig":{"maxOutputTokens":65536,"thinkingConfig":{"thinkingBudget":0}},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get a recursive tree view of files and directories as a JSON structure.","name":"directory_tree","parameters":{"properties":{"path":{"description":"Directory to traverse","type":"string"}},"required":["path"],"type":"object"}},{"description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","name":"edit_file","parameters":{"properties":{"edits":{"description":"Edits to apply","items":{"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":"array"},"path":{"description":"File to edit","type":"string"}},"required":["path","edits"],"type":"object"}},{"description":"Get a detailed listing of all files and directories in a specified path.","name":"list_directory","parameters":{"properties":{"path":{"description":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},{"description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","name":"read_file","parameters":{"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":"integer"},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":"integer"},"path":{"description":"File to read","type":"string"}},"required":["path"],"type":"object"}},{"description":"Read the contents of multiple files simultaneously.","name":"read_multiple_files","parameters":{"properties":{"json":{"description":"Return result as JSON","type":"boolean"},"paths":{"description":"Files to read","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},{"description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","name":"search_files_content","parameters":{"properties":{"excludePatterns":{"description":"Patterns to exclude","items":{"type":"string"},"type":"array"},"is_regex":{"description":"Treat query as regex","type":"boolean"},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","type":"string"}},"required":["path","query"],"type":"object"}},{"description":"Create a new file or completely overwrite an existing file with new content.","name":"write_file","parameters":{"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["path","content"],"type":"object"}},{"description":"Create one or more new directories or nested directory structures.","name":"create_directory","parameters":{"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},{"description":"Remove one or more empty directories.","name":"remove_directory","parameters":{"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}}]}]} form: alt: - sse @@ -33,7 +33,7 @@ interactions: content_length: 0 host: generativelanguage.googleapis.com body: | - {"contents":[{"parts":[{"text":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n"}],"role":"user"},{"parts":[{"text":"## Filesystem Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output"}],"role":"user"},{"parts":[{"text":"How many files in testdata/working_dir? Only output the number."}],"role":"user"},{"parts":[{"functionCall":{"args":{"path":"testdata/working_dir"},"name":"list_directory"},"thoughtSignature":"c2tpcF90aG91Z2h0X3NpZ25hdHVyZV92YWxpZGF0b3I="}],"role":"model"},{"parts":[{"functionResponse":{"name":"call_3df8565b-a1ef-4490-95f9-5d94296d7687","response":{"result":"FILE README.me\n"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":65536,"thinkingConfig":{"thinkingBudget":0}},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get a recursive tree view of files and directories as a JSON structure.","name":"directory_tree","parameters":{"properties":{"path":{"description":"Directory to traverse","type":"string"}},"required":["path"],"type":"object"}},{"description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","name":"edit_file","parameters":{"properties":{"edits":{"description":"Edits to apply","items":{"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":"array"},"path":{"description":"File to edit","type":"string"}},"required":["path","edits"],"type":"object"}},{"description":"Get a detailed listing of all files and directories in a specified path.","name":"list_directory","parameters":{"properties":{"path":{"description":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},{"description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","name":"read_file","parameters":{"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":"integer"},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":"integer"},"path":{"description":"File to read","type":"string"}},"required":["path"],"type":"object"}},{"description":"Read the contents of multiple files simultaneously.","name":"read_multiple_files","parameters":{"properties":{"json":{"description":"Return result as JSON","type":"boolean"},"paths":{"description":"Files to read","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},{"description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","name":"search_files_content","parameters":{"properties":{"excludePatterns":{"description":"Patterns to exclude","items":{"type":"string"},"type":"array"},"is_regex":{"description":"Treat query as regex","type":"boolean"},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","type":"string"}},"required":["path","query"],"type":"object"}},{"description":"Create a new file or completely overwrite an existing file with new content.","name":"write_file","parameters":{"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["path","content"],"type":"object"}},{"description":"Create one or more new directories or nested directory structures.","name":"create_directory","parameters":{"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},{"description":"Remove one or more empty directories.","name":"remove_directory","parameters":{"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}}]}]} + {"contents":[{"parts":[{"text":"You are a knowledgeable assistant that helps users with various tasks.\nBe helpful, accurate, and concise in your responses.\n"}],"role":"user"},{"parts":[{"text":"## Filesystem Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output"}],"role":"user"},{"parts":[{"text":"How many files in testdata/working_dir? Only output the number."}],"role":"user"},{"parts":[{"functionCall":{"args":{"path":"testdata/working_dir"},"name":"list_directory"},"thoughtSignature":"c2tpcF90aG91Z2h0X3NpZ25hdHVyZV92YWxpZGF0b3I="}],"role":"model"},{"parts":[{"functionResponse":{"name":"call_3df8565b-a1ef-4490-95f9-5d94296d7687","response":{"result":"FILE README.me\n"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":65536,"thinkingConfig":{"thinkingBudget":0}},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get a recursive tree view of files and directories as a JSON structure.","name":"directory_tree","parameters":{"properties":{"path":{"description":"Directory to traverse","type":"string"}},"required":["path"],"type":"object"}},{"description":"Make line-based edits to a text file. Each edit replaces exact line sequences with new content.","name":"edit_file","parameters":{"properties":{"edits":{"description":"Edits to apply","items":{"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["oldText","newText"],"type":"object"},"type":"array"},"path":{"description":"File to edit","type":"string"}},"required":["path","edits"],"type":"object"}},{"description":"Get a detailed listing of all files and directories in a specified path.","name":"list_directory","parameters":{"properties":{"path":{"description":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},{"description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","name":"read_file","parameters":{"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":"integer"},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":"integer"},"path":{"description":"File to read","type":"string"}},"required":["path"],"type":"object"}},{"description":"Read the contents of multiple files simultaneously.","name":"read_multiple_files","parameters":{"properties":{"json":{"description":"Return result as JSON","type":"boolean"},"paths":{"description":"Files to read","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},{"description":"Searches for text or regex patterns in the content of files matching a GLOB pattern.","name":"search_files_content","parameters":{"properties":{"excludePatterns":{"description":"Patterns to exclude","items":{"type":"string"},"type":"array"},"is_regex":{"description":"Treat query as regex","type":"boolean"},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","type":"string"}},"required":["path","query"],"type":"object"}},{"description":"Create a new file or completely overwrite an existing file with new content.","name":"write_file","parameters":{"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["path","content"],"type":"object"}},{"description":"Create one or more new directories or nested directory structures.","name":"create_directory","parameters":{"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},{"description":"Remove one or more empty directories.","name":"remove_directory","parameters":{"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}}]}]} form: alt: - sse diff --git a/e2e/testdata/cassettes/TestExec_Mistral_ToolCall.yaml b/e2e/testdata/cassettes/TestExec_Mistral_ToolCall.yaml index ab0a21e909..65fb6f20a7 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 Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"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 Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.mistral.ai/v1/chat/completions method: POST response: @@ -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 Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"D9WYdiHxV","function":{"arguments":"{\"path\": \"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"D9WYdiHxV","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"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 Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"D9WYdiHxV","function":{"arguments":"{\"path\": \"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"D9WYdiHxV","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.mistral.ai/v1/chat/completions method: POST response: diff --git a/e2e/testdata/cassettes/TestExec_OpenAI_HideToolCalls.yaml b/e2e/testdata/cassettes/TestExec_OpenAI_HideToolCalls.yaml index 7702ab6f12..29fc44f7d3 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 Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"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 Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.openai.com/v1/chat/completions method: POST response: @@ -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 Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"call_dsl9jWekN0H1do1ClfeyR1iA","function":{"arguments":"{\"path\":\"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"call_dsl9jWekN0H1do1ClfeyR1iA","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"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 Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"call_dsl9jWekN0H1do1ClfeyR1iA","function":{"arguments":"{\"path\":\"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"call_dsl9jWekN0H1do1ClfeyR1iA","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.openai.com/v1/chat/completions method: POST response: diff --git a/e2e/testdata/cassettes/TestExec_OpenAI_ToolCall.yaml b/e2e/testdata/cassettes/TestExec_OpenAI_ToolCall.yaml index 36600c9291..50930716b2 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 Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"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 Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.openai.com/v1/chat/completions method: POST response: @@ -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 Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"call_I1tmAsYKD7bveEpXORJwVFgs","function":{"arguments":"{\"path\":\"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"call_I1tmAsYKD7bveEpXORJwVFgs","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"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 Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","role":"system"},{"content":"How many files in testdata/working_dir? Only output the number.","role":"user"},{"tool_calls":[{"id":"call_I1tmAsYKD7bveEpXORJwVFgs","function":{"arguments":"{\"path\":\"testdata/working_dir\"}","name":"list_directory"},"type":"function"}],"role":"assistant"},{"content":"FILE README.me\n","tool_call_id":"call_I1tmAsYKD7bveEpXORJwVFgs","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":"Directory to traverse","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":"Edits to apply","items":{"additionalProperties":false,"properties":{"newText":{"description":"Replacement text","type":"string"},"oldText":{"description":"Exact text to replace","type":"string"}},"required":["newText","oldText"],"type":"object"},"type":"array"},"path":{"description":"File 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":"Directory to list","type":"string"}},"required":["path"],"type":"object"}},"type":"function"},{"function":{"name":"read_file","description":"Read the contents of a file from the file system. By default the complete file is returned; for text files the optional line (1-based start line) and limit (maximum number of lines) arguments select a line range. Supports text files and images (jpg, png, gif, webp). Images are returned as image content that you can view directly.","parameters":{"additionalProperties":false,"properties":{"limit":{"description":"Maximum number of lines to read (text files only; defaults to reading through the end of the file)","type":["null","integer"]},"line":{"description":"1-based line number to start reading from (text files only; defaults to the first line)","type":["null","integer"]},"path":{"description":"File to read","type":"string"}},"required":["limit","line","path"],"type":"object"}},"type":"function"},{"function":{"name":"read_multiple_files","description":"Read the contents of multiple files simultaneously.","parameters":{"additionalProperties":false,"properties":{"json":{"description":"Return result as JSON","type":["boolean","null"]},"paths":{"description":"Files to read","items":{"type":"string"},"type":"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","items":{"type":"string"},"type":["null","array"]},"is_regex":{"description":"Treat query as regex","type":["boolean","null"]},"path":{"description":"Starting directory","type":"string"},"query":{"description":"Text or regex to search","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":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"}},"type":"function"},{"function":{"name":"create_directory","description":"Create one or more new directories or nested directory structures.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to create","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"},{"function":{"name":"remove_directory","description":"Remove one or more empty directories.","parameters":{"additionalProperties":false,"properties":{"paths":{"description":"Directories to remove","items":{"type":"string"},"type":"array"}},"required":["paths"],"type":"object"}},"type":"function"}],"stream":true}' url: https://api.openai.com/v1/chat/completions method: POST response: diff --git a/e2e/testdata/cassettes/TestExec_ToolCallsNeedAcceptance.yaml b/e2e/testdata/cassettes/TestExec_ToolCallsNeedAcceptance.yaml index 8c90b57c5d..0f19ed2a19 100644 --- a/e2e/testdata/cassettes/TestExec_ToolCallsNeedAcceptance.yaml +++ b/e2e/testdata/cassettes/TestExec_ToolCallsNeedAcceptance.yaml @@ -8,7 +8,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.openai.com - body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"}],"model":"gpt-5-mini","reasoning":{"summary":"detailed"},"tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}' + body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"}],"model":"gpt-5-mini","reasoning":{"summary":"detailed"},"tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}' url: https://api.openai.com/v1/responses method: POST response: @@ -622,7 +622,7 @@ interactions: proto_minor: 1 content_length: 0 host: api.openai.com - body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tools\n\n- The working directory is \"/tmp/wd\"; relative paths resolve from it\n- Absolute paths must match the host OS (e.g. C:\\... on Windows, /... on Unix)\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"},{"arguments":"{\"content\":\"Hello, World!\",\"path\":\"hello.txt\"}","call_id":"call_GdIrV330GhotzkZcKWdlNbPV","name":"write_file","type":"function_call"},{"output":"The user rejected the tool call.","call_id":"call_GdIrV330GhotzkZcKWdlNbPV","type":"function_call_output"}],"model":"gpt-5-mini","reasoning":{"summary":"detailed"},"tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}' + body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"},{"arguments":"{\"content\":\"Hello, World!\",\"path\":\"hello.txt\"}","call_id":"call_GdIrV330GhotzkZcKWdlNbPV","name":"write_file","type":"function_call"},{"output":"The user rejected the tool call.","call_id":"call_GdIrV330GhotzkZcKWdlNbPV","type":"function_call_output"}],"model":"gpt-5-mini","reasoning":{"summary":"detailed"},"tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"File content","type":"string"},"path":{"description":"File to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}' url: https://api.openai.com/v1/responses method: POST response: diff --git a/pkg/fake/proxy.go b/pkg/fake/proxy.go index 615c5f40b5..965fe09969 100644 --- a/pkg/fake/proxy.go +++ b/pkg/fake/proxy.go @@ -294,9 +294,6 @@ func DefaultMatcher(onError func(err error)) recorder.MatcherFunc { toolChoiceRegex := regexp.MustCompile(`"tool_choice":"[^"]*",?`) // Normalize prompt-file paths (they are machine-specific absolute paths). promptFileRegex := regexp.MustCompile(`Instructions from: (?:[^\\"\r\n]|\\\\)+`) - // Normalize the working directory stated in the filesystem toolset - // instructions (machine-specific absolute path). - workingDirRegex := regexp.MustCompile(`The working directory is \\"(?:[^\\"\r\n]|\\\\)+\\"`) return func(r *http.Request, i cassette.Request) bool { if r.Body == nil || r.Body == http.NoBody { @@ -328,14 +325,12 @@ func DefaultMatcher(onError func(err error)) recorder.MatcherFunc { normalizedReq = reasoningRegex.ReplaceAllString(normalizedReq, "") normalizedReq = toolChoiceRegex.ReplaceAllString(normalizedReq, "") normalizedReq = promptFileRegex.ReplaceAllString(normalizedReq, "Instructions from: FILE") - normalizedReq = workingDirRegex.ReplaceAllString(normalizedReq, "The working directory is WD") normalizedCassette := callIDRegex.ReplaceAllString(i.Body, "call_ID") normalizedCassette = maxTokensRegex.ReplaceAllString(normalizedCassette, "") normalizedCassette = thinkingConfigRegex.ReplaceAllString(normalizedCassette, "") normalizedCassette = reasoningRegex.ReplaceAllString(normalizedCassette, "") normalizedCassette = toolChoiceRegex.ReplaceAllString(normalizedCassette, "") normalizedCassette = promptFileRegex.ReplaceAllString(normalizedCassette, "Instructions from: FILE") - normalizedCassette = workingDirRegex.ReplaceAllString(normalizedCassette, "The working directory is WD") return normalizedReq == normalizedCassette } diff --git a/pkg/tools/builtin/filesystem/filesystem.go b/pkg/tools/builtin/filesystem/filesystem.go index 1fab47cc94..bcd837415c 100644 --- a/pkg/tools/builtin/filesystem/filesystem.go +++ b/pkg/tools/builtin/filesystem/filesystem.go @@ -15,7 +15,6 @@ import ( "os" "path/filepath" "regexp" - "runtime" "strings" "sync" "unicode/utf8" @@ -250,12 +249,9 @@ func (t *ToolSet) Close() error { func (t *ToolSet) Instructions() string { var b strings.Builder - b.WriteString("## Filesystem Tools\n") - if t.workingDir != "" { - fmt.Fprintf(&b, "\n- The working directory is %q; relative paths resolve from it", t.workingDir) - } - b.WriteString(` -- Absolute paths must match the host OS (e.g. C:\... on Windows, /... on Unix) + b.WriteString(`## Filesystem Tools + +- Relative paths resolve from the working directory; absolute paths and ".." work as expected - Prefer read_multiple_files over sequential read_file calls - Use search_files_content to locate code or text across files - Use exclude patterns in searches and max_depth in directory_tree to limit output`) @@ -638,9 +634,9 @@ func (t *ToolSet) executePostEditCommands(ctx context.Context, filePath string) // resolvePath resolves a path relative to the working directory. // A leading "~" or "~/" is expanded to the user's home directory so that // LLM-supplied paths like "~/file.txt" work without the agent having to -// know the user's home directory upfront. Absolute paths are used as-is; -// everything else (including "." and "..") is joined with the working -// directory. +// know the user's home directory upfront. Relative paths (including ".") +// are joined with the working directory. Absolute paths and paths +// starting with ".." are used as-is. // // resolvePath does NOT enforce the allow- or deny-lists; callers should use // [resolveAndCheckPath] when those checks are required (i.e. for any path @@ -656,43 +652,6 @@ func (t *ToolSet) resolvePath(path string) string { return filepath.Clean(filepath.Join(t.workingDir, path)) } -// checkForeignPath rejects paths that are absolute in another OS's syntax -// (e.g. "/mnt/c/..." on Windows, "C:\..." on Unix). filepath.IsAbs does not -// recognize them, so resolvePath would silently join them onto the working -// directory and the caller would get a baffling not-found for a mangled -// path. An explicit "./" prefix bypasses the check for the rare legitimate -// file whose name matches one of these shapes. -func checkForeignPath(path, goos string) error { - if goos == "windows" { - switch { - // "//server/share" and "\\server\share" are valid UNC paths. - case strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "//"): - return fmt.Errorf("path %q looks like a POSIX absolute path, but this host runs Windows; use a Windows path (e.g. C:\\...) or a path relative to the working directory", path) - case strings.HasPrefix(path, `\`) && !strings.HasPrefix(path, `\\`): - return fmt.Errorf("path %q is rooted but has no drive letter; use a full Windows path (e.g. C:\\...) or a path relative to the working directory", path) - case isDriveRelative(path): - return fmt.Errorf("path %q is drive-relative; use a full Windows path (e.g. C:\\...) or a path relative to the working directory", path) - } - return nil - } - if hasDrivePrefix(path) && len(path) > 2 && (path[2] == '/' || path[2] == '\\') { - return fmt.Errorf("path %q looks like a Windows absolute path, but this host runs %s; use a POSIX path (e.g. /home/...) or a path relative to the working directory", path, goos) - } - return nil -} - -// hasDrivePrefix reports whether path starts with a drive letter and colon. -func hasDrivePrefix(path string) bool { - return len(path) >= 2 && path[1] == ':' && - ('a' <= path[0] && path[0] <= 'z' || 'A' <= path[0] && path[0] <= 'Z') -} - -// isDriveRelative reports whether path is like "C:" or "C:foo" — relative -// to the current directory of drive C:, which filepath.Join cannot represent. -func isDriveRelative(path string) bool { - return hasDrivePrefix(path) && (len(path) == 2 || path[2] != '/' && path[2] != '\\') -} - // resolveAndCheckPath is the canonical entry point used by every filesystem // handler that operates on a user-supplied path. It resolves the path against // the working directory and validates the result against the allow- and @@ -736,9 +695,6 @@ func (t *ToolSet) resolveAndCheckPath(path string) (string, error) { if t.sandboxBroken { return "", errors.New("filesystem toolset is disabled due to invalid allow/deny list configuration") } - if err := checkForeignPath(path, runtime.GOOS); err != nil { - return "", err - } resolved := t.resolvePath(path) if t.agentsIgnore.Match(resolved) { @@ -760,12 +716,6 @@ func (t *ToolSet) resolveAndCheckPath(path string) (string, error) { return resolved, nil } -// resolutionHint tells the model where a failed path actually pointed, so -// it can correct a wrong base instead of retrying the same path. -func (t *ToolSet) resolutionHint(resolved string) string { - return fmt.Sprintf(" (resolved to %q; relative paths resolve from the working directory %q)", resolved, t.workingDir) -} - // rootedAccess returns the [*os.Root] handle and rooted (slash-separated) // name for resolved when the allow-list is configured. // @@ -1019,9 +969,6 @@ func (t *ToolSet) handleEditFile(ctx context.Context, args EditFileArgs) (*tools content, err := t.readFile(resolvedPath) if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return tools.ResultError("not found" + t.resolutionHint(resolvedPath)), nil - } return tools.ResultError(fmt.Sprintf("Error reading file: %s", err)), nil } @@ -1066,9 +1013,6 @@ func (t *ToolSet) handleListDirectory(ctx context.Context, args ListDirectoryArg entries, err := t.readDir(resolvedPath) if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return tools.ResultError("not found" + t.resolutionHint(resolvedPath)), nil - } return tools.ResultError(fmt.Sprintf("Error reading directory: %s", err)), nil } @@ -1138,7 +1082,7 @@ func (t *ToolSet) handleReadFile(ctx context.Context, args ReadFileArgs) (*tools if err != nil { var errMsg string if errors.Is(err, fs.ErrNotExist) { - errMsg = "not found" + t.resolutionHint(resolvedPath) + errMsg = "not found" } else { errMsg = err.Error() } @@ -1147,7 +1091,6 @@ func (t *ToolSet) handleReadFile(ctx context.Context, args ReadFileArgs) (*tools Output: errMsg, IsError: true, Meta: ReadFileMeta{ - Path: args.Path, Error: errMsg, }, }, nil @@ -1375,7 +1318,7 @@ func (t *ToolSet) handleReadMultipleFiles(ctx context.Context, args ReadMultiple if err != nil { errMsg := err.Error() if errors.Is(err, fs.ErrNotExist) { - errMsg = "not found" + t.resolutionHint(resolvedPath) + errMsg = "not found" } contents = append(contents, PathContent{ Path: path, diff --git a/pkg/tools/builtin/filesystem/filesystem_paths_test.go b/pkg/tools/builtin/filesystem/filesystem_paths_test.go index 28f9095b08..4259730968 100644 --- a/pkg/tools/builtin/filesystem/filesystem_paths_test.go +++ b/pkg/tools/builtin/filesystem/filesystem_paths_test.go @@ -1,10 +1,8 @@ package filesystem import ( - "fmt" "os" "path/filepath" - "runtime" "testing" "github.com/stretchr/testify/assert" @@ -48,59 +46,6 @@ func TestFilesystemTool_DefaultIsUnrestricted(t *testing.T) { assert.Equal(t, want, resolved) } -func TestCheckForeignPath(t *testing.T) { - t.Parallel() - tests := []struct { - path string - goos string - reject bool - }{ - {"/mnt/c/Users/x/compose.yml", "windows", true}, - {`\foo\bar`, "windows", true}, - {"C:foo", "windows", true}, - {"C:", "windows", true}, - {`C:\Users\x`, "windows", false}, - {"C:/Users/x", "windows", false}, - {"//server/share", "windows", false}, - {`\\server\share`, "windows", false}, - {"./mnt/c/x", "windows", false}, - {"rel/path", "windows", false}, - {"~/file.txt", "windows", false}, - {`C:\Users\x`, "linux", true}, - {"C:/Users/x", "darwin", true}, - {"/mnt/c/Users/x", "linux", false}, - {`./C:\weird`, "linux", false}, - {"C:foo", "linux", false}, - {"rel/path", "linux", false}, - } - for _, tt := range tests { - t.Run(tt.goos+" "+tt.path, func(t *testing.T) { - err := checkForeignPath(tt.path, tt.goos) - if tt.reject { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -// A foreign-OS absolute path must be rejected with guidance instead of -// silently joining onto the working directory (issue seen on Windows with -// "/mnt/c/..." resolving to "\mnt\c\..."). -func TestResolveAndCheckPath_RejectsForeignPath(t *testing.T) { - t.Parallel() - tool := New(t.TempDir()) - - foreign := `C:\Users\x\compose.yml` - if runtime.GOOS == "windows" { - foreign = "/mnt/c/Users/x/compose.yml" - } - _, err := tool.resolveAndCheckPath(foreign) - require.Error(t, err) - assert.Contains(t, err.Error(), "relative to the working directory") -} - func TestFilesystemTool_AllowList_DotMeansWorkingDir(t *testing.T) { t.Parallel() tmpDir := t.TempDir() @@ -406,16 +351,11 @@ func TestFilesystemTool_Instructions_MentionsRestrictions(t *testing.T) { t.Parallel() wd := t.TempDir() - // Default instructions: working directory stated (%q-quoted), no - // restriction text. + // Default instructions: no restriction text. plain := New(wd).Instructions() - assert.Contains(t, plain, fmt.Sprintf("%q", wd)) assert.NotContains(t, plain, "restricted") assert.NotContains(t, plain, "must not access") - // No working directory configured: no dangling `The working directory is ""`. - assert.NotContains(t, New("").Instructions(), "working directory is") - // With an allow-list: instructions mention the restriction. allowed := newTestToolSet(t, wd, WithAllowList([]string{".", "~"})).Instructions() assert.Contains(t, allowed, "restricted") diff --git a/pkg/tools/builtin/filesystem/filesystem_test.go b/pkg/tools/builtin/filesystem/filesystem_test.go index 1165e5bf5f..3c7f7fa9dc 100644 --- a/pkg/tools/builtin/filesystem/filesystem_test.go +++ b/pkg/tools/builtin/filesystem/filesystem_test.go @@ -3,7 +3,6 @@ package filesystem import ( "bytes" "encoding/json" - "fmt" "image" "image/color" "image/jpeg" @@ -179,12 +178,7 @@ func TestFilesystemTool_ReadFile(t *testing.T) { Path: "nonexistent.txt", }) require.NoError(t, err) - assert.Contains(t, result.Output, "not found") - // The hint must name the resolved path and the working directory so the - // model can correct a wrong base instead of retrying the same path. - // %q-quoted, so Windows separators appear escaped in the output. - assert.Contains(t, result.Output, fmt.Sprintf("%q", filepath.Join(tmpDir, "nonexistent.txt"))) - assert.Contains(t, result.Output, fmt.Sprintf("%q", tmpDir)) + assert.Equal(t, "not found", result.Output) } // TestFilesystemTool_ReadFile_LineRange is a regression test for issue @@ -407,7 +401,7 @@ func TestFilesystemTool_ReadImageFile(t *testing.T) { result, err = tool.handleReadFile(t.Context(), ReadFileArgs{Path: "missing.png"}) require.NoError(t, err) assert.True(t, result.IsError) - assert.Contains(t, result.Output, "not found") + assert.Equal(t, "not found", result.Output) } func TestFilesystemTool_ReadMultipleFiles(t *testing.T) { @@ -462,7 +456,7 @@ func TestFilesystemTool_ListDirectory(t *testing.T) { Path: "nonexistent", }) require.NoError(t, err) - assert.Contains(t, result.Output, "not found") + assert.Contains(t, result.Output, "Error reading directory") } // TestFilesystemTool_ListDirectoryEmpty pins the empty-directory message: From 6fe0c4c256277a7a0867680ee5948332e056fa2c Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 10 Sep 2026 15:37:25 +0200 Subject: [PATCH 2/2] fix(ci): build fork PR images without cloud credentials Assisted-By: docker-agent --- .github/workflows/ci.yml | 65 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfd1a59dc8..7207485829 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -184,7 +184,8 @@ jobs: github.repository == 'docker/docker-agent' && github.event.repository.fork == false && ( (github.event_name == 'pull_request' && - github.event.pull_request.base.repo.full_name == github.repository) || + github.event.pull_request.base.repo.full_name == github.repository && + github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/heads/') && (github.ref != 'refs/heads/main' || inputs.image_mode == 'build-only') && @@ -303,6 +304,68 @@ jobs: GIT_TAG=pr GIT_COMMIT=dev + # Fork PRs cannot use repository variables or OIDC; build without credentials. + build-image-fork: + if: >- + github.repository == 'docker/docker-agent' && + github.event_name == 'pull_request' && + github.event.pull_request.base.repo.full_name == github.repository && + github.event.pull_request.head.repo.full_name != github.repository + permissions: + contents: read + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + with: + driver: docker-container + cache-binary: false + + - name: Build image + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + with: + context: . + platforms: ${{ matrix.platform }} + outputs: type=cacheonly + push: false + load: false + sbom: false + provenance: false + github-token: '' + build-args: | + GIT_TAG=pr + GIT_COMMIT=dev + + - name: Build template + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + with: + context: . + target: template + platforms: ${{ matrix.platform }} + outputs: type=cacheonly + push: false + load: false + sbom: false + provenance: false + github-token: '' + build-args: | + GIT_TAG=pr + GIT_COMMIT=dev + build-and-push-image: if: >- github.repository == 'docker/docker-agent' &&