Skip to content

feat(context)!: switch from contexts to function calling #1029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 46 commits into
base: main
Choose a base branch
from

Conversation

deathbeam
Copy link
Collaborator

@deathbeam deathbeam commented Mar 17, 2025

This change modernizes the CopilotChat architecture by implementing a tools-based
approach using function calling. Each tool has a schema definition that enables
structured parameter collection and validation. Resources are now handled more
consistently, with a clearer distinction between content types.

https://platform.openai.com/docs/guides/function-calling?api-mode=responses
https://modelcontextprotocol.info/specification/2024-11-05/server/tools/

BREAKING CHANGE: The context API has changed from callback-based input handling to schema-based definitions.
BREAKING CHANGE: config.contexts renamed to config.tools
BREAKING CHANGE: config.context removed, use config.sticky
BREAKING CHANGE: diagnostics moved to separate tool call, selection and buffer calls no longer include them by default
BREAKING CHANGE: non-resource based tool calls can no longer be soft stored in sticky, now they are auto expanded to promot
BREAKING CHANGE: viewing full context is no longer possible (as now tools can have bigger side effects), gi renamed to gc, now also includes selection
BREAKING CHANGE: filenames renamed to glob
BREAKING CHANGE: files removed (use glob together with tool calling instead, or buffers/quickfix)
BREAKING CHANGE: copilot extension agents removed, tools + mcp servers can replace this feature and maintaining them was pain, they can still be implemented via custom providers anyway
BREAKING CHANGE: actions and integrations action removed as they were deprecated for a while

Closes #1045
Closes #1053
Closes #1076
Closes #1090
Closes #1096
Closes #526

TODO:

  • Improve response format from contexts
  • Maybe rename contexts to tools?
  • Add names of tool to embedded context value
  • Inline contexts/tools - expand directly to user prompt
  • Split diagnostics to separate context
  • Mark built in tools as safe, mark everything else unsafe
  • How to resolve MCP resources? How to resolve built in resources? Currently hardcoded for file and url, not good
  • Rest of commands resources for reuse? git?
  • Properly store tool responses using "tool" role and also send tool_calls in history
    • When tool_call response is received, new tool_call prompt, user submitable, editable, sends only tools
    • On submit replace with tool_call responses, send proper "tool" role, wait response
  • Parse resource template to schema for input, pass uri to input for resolution
  • Rename agent to group, allow sharing single tools as well

https://github.com/ravitemer/mcphub.nvim integration:

local mcp = require('mcphub')
mcp.setup()
mcp.on({ 'servers_updated', 'tool_list_changed', 'resource_list_changed' }, function()
    local hub = mcp.get_hub_instance()
    if not hub then
        return
    end

    local async = require('plenary.async')
    local call_tool = async.wrap(function(server, tool, input, callback)
        hub:call_tool(server, tool, input, {
            callback = function(res, err)
                callback(res, err)
            end,
        })
    end, 4)

    local access_resource = async.wrap(function(server, uri, callback)
        hub:access_resource(server, uri, {
            callback = function(res, err)
                callback(res, err)
            end,
        })
    end, 3)

    local resources = hub:get_resources()
    for _, resource in ipairs(resources) do
        local name = resource.name:lower():gsub(' ', '_'):gsub(':', '')
        chat.config.functions[name] = {
            uri = resource.uri,
            description = type(resource.description) == 'string' and resource.description or '',
            resolve = function()
                local res, err = access_resource(resource.server_name, resource.uri)
                if err then
                    error(err)
                end

                res = res or {}
                local result = res.result or {}
                local content = result.contents or {}
                local out = {}

                for _, message in ipairs(content) do
                    if message.text then
                        table.insert(out, {
                            uri = message.uri,
                            data = message.text,
                            mimetype = message.mimeType,
                        })
                    end
                end

                return out
            end
        }
    end

    local tools = hub:get_tools()
    for _, tool in ipairs(tools) do
        chat.config.functions[tool.name] = {
            group = tool.server_name,
            description = tool.description,
            schema = tool.inputSchema,
            resolve = function(input)
                local res, err = call_tool(tool.server_name, tool.name, input)
                if err then
                    error(err)
                end

                res = res or {}
                local result = res.result or {}
                local content = result.content or {}
                local out = {}

                for _, message in ipairs(content) do
                    if message.type == 'text' then
                        table.insert(out, {
                            data = message.text
                        })
                    elseif message.type == 'resource' and message.resource and message.resource.text then
                        table.insert(out, {
                            uri = message.resource.uri,
                            data = message.resource.text,
                            mimetype = message.resource.mimeType,
                        })
                    end
                end

                return out
            end
        }
    end
end)

@deathbeam deathbeam added the enhancement New feature or request label Mar 17, 2025
@deathbeam deathbeam force-pushed the tools branch 5 times, most recently from 7885523 to 41c7751 Compare March 17, 2025 19:14
@deathbeam deathbeam marked this pull request as ready for review March 17, 2025 19:17
@deathbeam deathbeam force-pushed the tools branch 5 times, most recently from 24fed0a to b6c7b19 Compare March 17, 2025 20:00
@deathbeam deathbeam changed the title feat(context): implement schema-based input for providers feat(context)!: implement schema-based input for providers Mar 17, 2025
@deathbeam deathbeam changed the title feat(context)!: implement schema-based input for providers feat(context)!: implement schema-based input for contexts Mar 17, 2025
@deathbeam deathbeam force-pushed the tools branch 2 times, most recently from a23ccfa to eaba359 Compare March 17, 2025 20:03
@deathbeam deathbeam changed the title feat(context)!: implement schema-based input for contexts feat(context)!: implement schema-based context providers Mar 17, 2025
@deathbeam deathbeam changed the title feat(context)!: implement schema-based context providers feat(context)!: implement schema-based contexts Mar 17, 2025
@deathbeam deathbeam force-pushed the tools branch 8 times, most recently from f4e0432 to cc4b04f Compare March 17, 2025 21:24
@deathbeam deathbeam marked this pull request as draft March 17, 2025 21:33
@deathbeam deathbeam force-pushed the tools branch 2 times, most recently from baa2848 to 7604698 Compare March 18, 2025 08:46
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
@tebriel
Copy link

tebriel commented Apr 29, 2025

👋🏻 I've set up:

  1. your branch of CopilotChat.nvim
  2. the example mcphub config in the description
  3. mcphub (4.11, 5.0 is out today but that seemed like it might be a problem)
  4. 2 different MCP Servers (github-mcp, kagi-mcp), 2 different Native Servers (MCPHub and Neovim)

I'm unsure how to tie CopilotChat into these tools. I see in screenshots above you do can you show me my pull requests however Copilot (claude 3.7 sonnet) cannot see any tools. Are there configurations that I need to change to my CopilotChat config (beyond the event listener in the description lua code) to link these two up?

@deathbeam
Copy link
Collaborator Author

👋🏻 I've set up:

  1. your branch of CopilotChat.nvim
  2. the example mcphub config in the description
  3. mcphub (4.11, 5.0 is out today but that seemed like it might be a problem)
  4. 2 different MCP Servers (github-mcp, kagi-mcp), 2 different Native Servers (MCPHub and Neovim)

I'm unsure how to tie CopilotChat into these tools. I see in screenshots above you do can you show me my pull requests however Copilot (claude 3.7 sonnet) cannot see any tools. Are there configurations that I need to change to my CopilotChat config (beyond the event listener in the description lua code) to link these two up?

you need to reference the tool or mcp server in chat with @, so for example @github etc, depends on how you named the server in your config

@tebriel
Copy link

tebriel commented Apr 30, 2025

@deathbeam thanks that makes perfect sense! I'm close, I just don't seem to be getting the response? My first line was @github can you show me my prs? and it [properly I assume] removed the @github.

I have Auto Approve: ON in MCPHub so I expected it to execute the get_me line. When it put the #get_me:tooluse... in my chat it just sat, after a few minutes I hit my submit keybinding and then that's how I got the ## Copilot response below it.

## User ───

can you show me my prs?

## Copilot ───

I'll help you retrieve your pull requests. Let me get information about your GitHub account first.

## User ───

#get_me:tooluse_CiBS6aLDQ4iuSXaXqb-HWw

## Copilot ───

I don't have the ability to access your GitHub pull requests directly. To view your open PRs, you would need to use the GitHub CLI (`gh`) or check them in a web browser.

If you have the GitHub CLI installed, you can run:

\```bash
gh pr list --author @me
\```

Or visit GitHub in your browser at https://github.com/pulls to see all your open pull requests across repositories.

@deathbeam
Copy link
Collaborator Author

deathbeam commented Apr 30, 2025

im currently in process of changing how tools are used, but for now, you need to also make the tool reference sticky, so add

> @github

etc

@tebriel
Copy link

tebriel commented Apr 30, 2025

Gotcha, that works! I'll try to do some debugging if I can find some time today, just had an exception at https://github.com/CopilotC-Nvim/CopilotChat.nvim/pull/1029/files#diff-7a2411a15623062457419a57f69def5a63e571e69f65b413b402a4a372c056a7R343 I may have something misconfigured though so unless it sparks immediate "oh this is wrong" I'll look into it. I not expecting you to provide me support on your PR, just hoping to help test, validate, and learn.

Error executing vim.schedule lua callback: ...mPackages/start/plenary.nvim/lua/plenary/async/async.lua:18: The coroutine failed with this message: ...Packages/start/CopilotChat.nvim/lua/CopilotChat/init.lua:343:
 invalid capture index
stack traceback:
        [C]: in function 'error'
        ...mPackages/start/plenary.nvim/lua/plenary/async/async.lua:18: in function 'callback_or_next'
        ...mPackages/start/plenary.nvim/lua/plenary/async/async.lua:45: in function 'callback'
        /home/tebriel/.config/nvim/init.lua:146: in function 'original_callback'
        ...mPackages/start/vimplugin-mcphub-nvim/lua/mcphub/hub.lua:432: in function 'callback'
        ...mPackages/start/vimplugin-mcphub-nvim/lua/mcphub/hub.lua:659: in function 'process_response'
        ...mPackages/start/vimplugin-mcphub-nvim/lua/mcphub/hub.lua:670: in function ''
        vim/_editor.lua: in function <vim/_editor.lua:0>

@deathbeam
Copy link
Collaborator Author

Yea i noticed that issue as well, I think it has something to do with weird filenames (good example is jdt:// stuff in java decompiled classes etc)

@tebriel
Copy link

tebriel commented Apr 30, 2025

I'll take a crack at the pattern stuff I do enjoy a good regex.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
3 participants