Skip to content

report tool calls while their arguments stream (LSP progress and ACP preview) #53

Open

Description

@editvolt

`

Summary

In agent mode, a client learns nothing about a tool call until the model has finished generating all of its arguments. For file-writing tools this means a long silence. A large create_file (about 12.5k characters) leaves the client idle for 30–40 s, with no indication that a file is being written or which one. The server already receives the arguments in pieces from the model; it just doesn't forward them. We're asking for an opt-in way to receive them, in LSP mode and in the ACP preview.

Environment

  • @github/copilot-language-server 1.551.2 (latest on npm), macOS arm64
  • LSP mode (--stdio): conversation/create with chatMode: "Agent", client tools registered with conversation/registerTools
  • Model: Claude Sonnet 5.5 (similar behaviour with GPT models)

What we observed (LSP mode)

We logged every JSON-RPC message in both directions, with millisecond timestamps, for a prompt that writes one large index.html through a client create_file tool:

Time Message
+0.000 s → conversation/create
+0.686 s ← $/progress begin
+2.533 s ← window/logMessage "[fetchChat] … finished with 200 status" (model response started)
+2.534 s → +39.226 s no messages of any kind, in either direction (36.7 s)
+39.227 s ← $/progress report: editAgentRounds[0].toolCalls[0] = { name: "create_file", status: "running", input: { filePath, content: <all 12,505 chars> } }. This is the first message naming the tool or the file
+39.228 s ← conversation/invokeClientTool with the same complete input

Reply text does stream live in the same turn. Only the tool-call arguments are held back.

Why (from reading the published dist/main.js)

  • The stream handlers for the Messages, Responses and Chat Completions APIs already produce beginToolCalls (name and id, at content_block_start / output_item.added) and copilotToolCallStreamUpdates (the arguments so far, on each input_json_delta / function_call_arguments.delta).
  • The only consumer of these fields appears to be FetchStreamRecorder (timing). The agent loop's stream callback (ConversationFinishCallback.isFinishedAfter) forwards text, annotations, references, errors and thinking, but not tool-call starts or argument deltas.
  • AgentToolCalls.init sets the status "not started", but that status is never reported. The first status a client sees is "running", with the arguments already complete.
  • We found no copilotCapabilities flag, initialization option or feature flag that changes this.

ACP (preview)

  • Through the server's --acp relay to the Copilot CLI (1.0.93, observed with a scripted model stand-in): a tool_call with status: "pending" arrives about 7 ms after the model finishes the arguments. It carries the title ("Creating …/index.html") and the full rawInput. During the 18.6 s of argument streaming, no session/update was sent.
  • The internal ACP pipeline (GITHUB_COPILOT_ACP_USE_CLI=0) builds its tool_call from the same progress reports, so we expect the same timing. We read this from the code but couldn't run it live.
  • The Copilot CLI's own SDK stream already exposes this (assistant.tool_call_delta with toolCallId, toolName, inputDelta), so the data and the event shape exist. Since ACP is in preview, this looks like a good time to carry it through ACP too.

Request (opt-in, no change for existing clients)

For example initializationOptions.copilotCapabilities.toolCallStreaming: true:

  1. Tool-call start. When the model starts a tool call, report it right away:
    • LSP: an editAgentRounds[].toolCalls[] entry with id, name and status: "not started" (or a new "streaming").
    • ACP: a tool_call with status: "pending" and the tool's title.
  2. Argument progress. Send throttled updates (for example every 100–250 ms) carrying the arguments accumulated so far, as raw partial JSON:
    • LSP: a partialInput field.
    • ACP: a tool_call_update with rawInput. That lets clients show the file path as soon as it appears, and the content growing.
  3. Same id throughout, so the call carries on into running / conversation/invokeClientTool and then completed.

Why it matters

Users see an idle spinner for the entire time a large file is being written, and it looks like the agent is stuck. Other agent hosts show "Creating <file>" and a growing line count during this phase. This change would let LSP and ACP clients do the same, with data the server already has.

`

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions