Skip to content

feat: Support overriding built-in tools#636

Merged
SteveSandersonMS merged 10 commits intomainfrom
stevesa/support-overriding-tools
Mar 3, 2026
Merged

feat: Support overriding built-in tools#636
SteveSandersonMS merged 10 commits intomainfrom
stevesa/support-overriding-tools

Conversation

@SteveSandersonMS
Copy link
Contributor

Summary

Adds support for SDK-registered tools to override CLI built-in tools of the same name (Issue #411).

Each tool can now declare overridesBuiltInTool: true to intentionally replace a built-in tool. The runtime uses this flag to detect accidental name clashes (error) vs intentional overrides.

Changes across all 4 SDKs (Node.js, Python, Go, .NET)

  • Add overridesBuiltInTool field to tool definitions
  • Pass overridesBuiltInTool through wire protocol to runtime
  • Update documentation with 'Overriding Built-in Tools' section in all READMEs
  • Add unit tests and E2E tests with snapshot scenarios

Closes #411

Copilot AI and others added 7 commits March 3, 2026 15:16
Auto-add user-registered tool names to excludedTools in session.create/resume
RPC payloads so that SDK-registered tools override CLI built-in tools.

- Node.js: mergeExcludedTools() helper + createSession/resumeSession updates
- Python: inline merge logic in create_session/resume_session
- Go: mergeExcludedTools() helper + CreateSession/ResumeSessionWithOptions updates
- .NET: MergeExcludedTools() helper + CreateSessionAsync/ResumeSessionAsync updates
- Tests added for all 4 SDKs
- All 4 READMEs updated with "Overriding Built-in Tools" documentation

Co-authored-by: patniko <26906478+patniko@users.noreply.github.com>
Add E2E tests across all 4 SDKs verifying that registering a custom tool
with the same name as a built-in tool (e.g., 'grep') causes the custom
tool to be invoked instead of the built-in. This validates the
mergeExcludedTools feature end-to-end.

- Add 'overrides built-in tool with custom tool' test to Node, Python, Go, .NET
- Add YAML snapshot for the replay proxy
- Add test/scenarios/tools/tool-overrides/ with all 4 language implementations

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix Python tests: add missing required argument to create_session() calls
- Fix Go: separate misplaced buildUnsupportedToolResult doc comment from mergeExcludedTools
- Fix Go sample: whitespace alignment from merge

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of merging SDK tool names into excludedTools (which semantically
abuses the exclude mechanism), each tool now declares overridesBuiltInTool
when it intentionally replaces a built-in tool. The runtime uses this flag
to detect accidental name clashes (error) vs intentional overrides.

Changes across all 4 SDKs (Node.js, Python, Go, .NET):
- Add overridesBuiltInTool field to tool definitions
- Remove mergeExcludedTools / inline merge logic
- Pass overridesBuiltInTool through wire protocol to runtime
- Update tests and documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS SteveSandersonMS requested a review from a team as a code owner March 3, 2026 15:20
Copilot AI review requested due to automatic review settings March 3, 2026 15:20
- Remove TestMergeExcludedTools from go/client_test.go (tests deleted function)
- Revert excluded_tools handling in python/copilot/client.py to 'is not None'
- Rename MergeExcludedToolsTests.cs to OverridesBuiltInToolTests.cs to match class

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dotnet/README.md Outdated

#### Overriding Built-in Tools

If you register a tool with the same name as a built-in CLI tool (e.g. `edit_file`, `read_file`), the SDK will throw an error unless you explicitly opt in by setting `BuiltInToolOverrides` on the session config. This flag signals that you intend to replace the built-in tool with your custom implementation.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to query for all built-in tools?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not currently. Can you say what the use case would be?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if there's a) a way I can proactively determine whether I'm going to get conflicts with built-in tools or if it'll just be "oops, that one conflicts now, better change it" and b) whether I can determine what tools are in use to see whether I might want to replace one with my own logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK thanks for explaining. I suspect even then it might be tricky to make a programmatic decision since it would depend on understanding the semantics and usage patterns of the built-in tool. But we can certainly consider adding a way to enumerate them if we get demand for it.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support across the SDKs for intentionally overriding Copilot CLI built-in tools by name, using an explicit opt-in flag/setting so accidental name clashes become errors while intentional overrides work.

Changes:

  • Add overridesBuiltInTool (or equivalent) to tool definitions and forward it over the JSON-RPC wire protocol.
  • Update SDK READMEs plus add scenario samples demonstrating a custom tool overriding a built-in tool.
  • Add unit + E2E coverage (including a new replay snapshot) for the override behavior.

Reviewed changes

Copilot reviewed 32 out of 33 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/snapshots/tools/overrides_built_in_tool_with_custom_tool.yaml New replay snapshot covering a custom grep tool override flow.
test/scenarios/tools/tool-overrides/verify.sh Scenario verification script that builds/runs all 4 language samples and checks for override output.
test/scenarios/tools/tool-overrides/typescript/src/index.ts TypeScript scenario showing overridesBuiltInTool: true on a custom grep tool.
test/scenarios/tools/tool-overrides/typescript/package.json Scenario package wiring to local Node SDK build.
test/scenarios/tools/tool-overrides/python/requirements.txt Scenario requirements pointing at editable local Python SDK.
test/scenarios/tools/tool-overrides/python/main.py Python scenario showing overrides_built_in_tool=True on a custom grep tool.
test/scenarios/tools/tool-overrides/go/main.go Go scenario showing OverridesBuiltInTool = true on a custom grep tool.
test/scenarios/tools/tool-overrides/go/go.mod Go scenario module with local replace to repo Go SDK.
test/scenarios/tools/tool-overrides/go/go.sum Go scenario dependency checksums.
test/scenarios/tools/tool-overrides/csharp/csharp.csproj C# scenario project referencing the local .NET SDK project.
test/scenarios/tools/tool-overrides/csharp/Program.cs C# scenario demonstrating override via session config BuiltInToolOverrides.
test/scenarios/tools/tool-overrides/README.md Scenario documentation for running/verifying tool overrides.
python/test_client.py Python unit tests validating overridesBuiltInTool serialization behavior.
python/e2e/test_tools.py Python E2E test for overriding a built-in tool with a custom tool.
python/copilot/types.py Adds overrides_built_in_tool to the Python Tool dataclass.
python/copilot/tools.py Extends define_tool to accept overrides_built_in_tool.
python/copilot/client.py Forwards overridesBuiltInTool in tool definitions over the wire protocol.
python/README.md Documents how to opt into overriding built-in tools in Python.
nodejs/test/e2e/tools.test.ts Node E2E test for built-in tool override behavior.
nodejs/test/client.test.ts Node unit tests verifying overridesBuiltInTool is sent and excluded tools aren’t auto-mutated.
nodejs/src/types.ts Adds overridesBuiltInTool?: boolean to Tool / defineTool types.
nodejs/src/client.ts Passes overridesBuiltInTool through to session.create / session.resume.
nodejs/README.md Documents how to opt into overriding built-in tools in Node/TS.
go/types.go Adds OverridesBuiltInTool to Go Tool struct for JSON serialization.
go/internal/e2e/tools_test.go Go E2E test for overriding a built-in tool with a custom tool.
go/client_test.go Go unit tests for tool serialization plus additional excluded-tools merging tests.
go/README.md Documents how to opt into overriding built-in tools in Go.
dotnet/test/ToolsTests.cs .NET E2E-style test verifying custom tool override works.
dotnet/test/MergeExcludedToolsTests.cs .NET unit tests for override flag plumbing + session config surface area.
dotnet/src/Types.cs Adds BuiltInToolOverrides to session configs and copy constructors.
dotnet/src/GitHub.Copilot.SDK.csproj Adds InternalsVisibleTo for SDK test assembly access.
dotnet/src/Client.cs Sets OverridesBuiltInTool on serialized tool definitions based on session config.
dotnet/README.md Documents how to opt into overriding built-in tools in .NET.
Comments suppressed due to low confidence (3)

python/copilot/client.py:503

  • excluded_tools is a list[str] in SessionConfig; converting via list(cfg.get("excluded_tools") or []) changes semantics (explicit [] becomes omitted) and can also accidentally turn a string into a list of characters. Prefer preserving the original value and using an is not None check (matching available_tools) or validating the type before serializing.

This issue also appears on line 681 of the same file.

        excluded_tools = cfg.get("excluded_tools")
        if excluded_tools is not None:
            payload["excludedTools"] = excluded_tools

python/copilot/client.py:683

  • Same issue as in create_session: excluded_tools = list(cfg.get("excluded_tools") or []) can change caller intent and coerce unexpected types. Preserve the provided list (including empty) and only omit the field when the key is truly absent/None.
        excluded_tools = cfg.get("excluded_tools")
        if excluded_tools is not None:
            payload["excludedTools"] = excluded_tools

go/client_test.go:577

  • The new tests call mergeExcludedTools(...), but no such function exists in the Go package (searching the repo shows only these call sites). This will fail to compile the test suite; either add the helper to the production code (if intended) or remove/adjust these tests to target the actual behavior.

Comment on lines 23 to 48
@overload
def define_tool(
name: str | None = None,
*,
description: str | None = None,
) -> Callable[[Callable[..., Any]], Tool]: ...


@overload
def define_tool(
name: str,
*,
description: str | None = None,
handler: Callable[[T, ToolInvocation], R],
params_type: type[T],
) -> Tool: ...


def define_tool(
name: str | None = None,
*,
description: str | None = None,
handler: Callable[[Any, ToolInvocation], Any] | None = None,
params_type: type[BaseModel] | None = None,
overrides_built_in_tool: bool = False,
) -> Tool | Callable[[Callable[[Any, ToolInvocation], Any]], Tool]:
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

define_tool’s overload signatures don’t include the new overrides_built_in_tool keyword-only parameter, so type checkers will flag valid calls (and docs/tests added in this PR already use it). Update both overloads to accept overrides_built_in_tool: bool = False.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +17
| Option | Value | Effect |
|--------|-------|--------|
| `tools` | Custom `grep` tool | Provides a custom grep implementation |
| `overridesBuiltInTool` | `true` | Tells the SDK to disable the built-in `grep` in favor of the custom one |

Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown table rows start with || which adds an unintended empty column / breaks typical table formatting. Use a single leading | for the header and separator rows.

Copilot uses AI. Check for mistakes.
…ove stale tests

- .NET: Read is_override from AIFunction.AdditionalProperties instead of
  BuiltInToolOverrides on session config. Remove BuiltInToolOverrides from
  SessionConfig and ResumeSessionConfig.
- Remove negative tests asserting old mergeExcludedTools behavior is gone
  (Node: 'does not merge tool names', 'preserves user-specified excludedTools';
  Python: test_does_not_merge_tool_names, test_preserves_user_excluded_tools)
- Update README and scenario examples
- Run formatters (gofmt, ruff, prettier, dotnet format)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github github deleted a comment from github-actions bot Mar 3, 2026
…d signatures

- Remove OverridesBuiltInToolTests.cs (E2E test in ToolsTests.cs covers this)
- Remove InternalsVisibleTo from csproj (no longer needed)
- Add overrides_built_in_tool param to both define_tool overload signatures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

✅ Cross-SDK Consistency Review: PASSED

I've completed a comprehensive consistency review of this PR across all 4 SDK implementations (Node.js/TypeScript, Python, Go, and .NET).

Review Summary

This PR adds support for overriding built-in tools with the overridesBuiltInTool feature, and it maintains excellent cross-SDK consistency. Here's what I verified:

✅ Feature Implementation

All 4 SDKs implement the feature following their language-specific conventions:

  • TypeScript: overridesBuiltInTool: boolean (optional field)
  • Python: overrides_built_in_tool: bool = False (decorator parameter)
  • Go: OverridesBuiltInTool bool (struct field with omitempty)
  • .NET: is_override in AdditionalProperties (consistent with AIFunction pattern)

✅ Wire Protocol Consistency

All SDKs correctly serialize to the same JSON field: "overridesBuiltInTool": true (camelCase on the wire)

✅ Documentation

All 4 READMEs include an "Overriding Built-in Tools" section with:

  • Clear explanation of when/why to use this feature
  • Language-specific code examples
  • Consistent messaging about error handling for accidental name clashes

✅ Test Coverage

  • Unit tests: All SDKs verify serialization and wire protocol correctness
  • E2E tests: All SDKs test actual override behavior with custom grep tool
  • Scenario tests: Cross-language scenario in test/scenarios/tools/tool-overrides/ validates consistency

Conclusion

No consistency issues found. This PR is a model example of maintaining feature parity across multi-language SDK implementations. The author has:

  • Implemented the feature in all 4 languages
  • Respected language-specific naming conventions
  • Ensured wire protocol compatibility
  • Provided comprehensive documentation and tests

Great work! 🎉

AI generated by SDK Consistency Review Agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support overriding built-in tools

5 participants