Skip to content

fix(agent/copilot): map reasoning deltas to EventThinking#1512

Open
zhengweixing wants to merge 3 commits into
chenhg5:mainfrom
zhengweixing:fix/copilot-reasoning-thinking
Open

fix(agent/copilot): map reasoning deltas to EventThinking#1512
zhengweixing wants to merge 3 commits into
chenhg5:mainfrom
zhengweixing:fix/copilot-reasoning-thinking

Conversation

@zhengweixing

@zhengweixing zhengweixing commented Jul 8, 2026

Copy link
Copy Markdown

Summary

When bridging GitHub Copilot CLI through cc-connect to a chat platform (e.g. Feishu),
Copilot's thinking/reasoning content leaks into the answer text and thinking_messages=false
cannot suppress it. This PR maps Copilot reasoning events to core.EventThinking so the
existing thinking-display toggle works.

Root cause

Copilot CLI streams reasoning as two distinct session.event types (captured by probing
copilot --stdio directly):

  • assistant.reasoning_delta (streaming): {"reasoningId":"...","deltaContent":"..."}
  • assistant.reasoning (final aggregated): {"reasoningId":"...","content":"..."}

The copilot adapter (agent/copilot/session.go) had no case for either — they fell into
the default branch and were dropped. Because reasoning was never tagged EventThinking,
thinking_messages=false (which only filters EventThinking) had no effect. Since the
deltaContent field name matches ordinary message deltas, reasoning could also be misrouted
as answer text and concatenated into the reply.

Confirmed absent on upstream main (a23fb90) and base commit (e2bbe5f): assistant.reasoning
occurs 0 times in agent/copilot/session.go before this PR.

Type of change

  • Bug fix (non-breaking change that fixes an issue)

Fix

  • handleSessionEvent: add assistant.reasoning_delta and assistant.reasoning cases,
    both emitting core.EventThinking (reusing copilotEventText, which already reads
    deltaContent/content).
  • normalizeCopilotEventType: normalize assistant_reasoning / assistant_reasoning_delta
    underscore variants.

Testing

Automated tests added in this PR

  • TestHandleSessionEvent_ReasoningDelta in agent/copilot/session_test.go — asserts
    assistant.reasoning_delta with deltaContent maps to core.EventThinking.
  • TestHandleSessionEvent_ReasoningFinal in agent/copilot/session_test.go — asserts
    assistant_reasoning (final, content) maps to core.EventThinking.

For bug fixes only — regression test

  • Regression test name: TestHandleSessionEvent_ReasoningDelta
  • Manual verification this test catches the regression:
    • Without the fix, assistant.reasoning_delta hits the default branch (no event emitted),
      so the test fails on pre-fix code and passes on the fixed code (verified by code inspection
      of the default branch on e2bbe5f).

Critical User Journeys (CUJ) impact

  • A — basic conversation
  • I — UI rendering correctness (cards, streaming, display modes)
  • go test ./core/ -run TestCUJ passes locally.

Manual / user-visible behavior change

With thinking_messages=false (e.g. CC_CONNECT_DISPLAY_THINKING_MESSAGES=false), Copilot
reasoning no longer appears in the chat reply. With it enabled, reasoning is shown as a
separate thinking preview rather than mixed inline with the answer.

Checklist (reviewer will verify)

  • go build ./... passes
  • go test ./... passes (copilot package; concurrency untouched, so no -race needed)
  • AGENTS.md Pre-Commit Checklist items are satisfied
  • No new hardcoded platform/agent names in core/
  • i18n strings have all-language translations (N/A — no new user-facing text)
  • No secrets / credentials in source

Related

zhengweixing and others added 2 commits July 8, 2026 11:44
Copilot CLI streams thinking/reasoning content as `assistant.reasoning_delta`
(streaming, with `deltaContent`) and `assistant.reasoning` (final, with
`content`). The copilot adapter had no case for either, so these events
fell through to the default handler and never emitted an event — meaning
reasoning could be misrouted or lost, and `thinking_messages=false` could
not suppress it because nothing was tagged as thinking.

Captured the real wire format by probing `copilot --stdio`: reasoning
deltas carry `{"reasoningId":"...","deltaContent":"..."}`, mirroring the
message delta shape, so they reuse `copilotEventText`.

Changes:
- handleSessionEvent: add `assistant.reasoning_delta` and
  `assistant.reasoning` cases, emitting `core.EventThinking`.
- normalizeCopilotEventType: normalize `assistant_reasoning` /
  `assistant_reasoning_delta` underscore variants.
- Add tests for reasoning delta and final reasoning -> EventThinking.

With this, `thinking_messages=false` correctly suppresses Copilot
reasoning and it no longer mixes into the answer text.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@zhengweixing zhengweixing requested a review from chenhg5 as a code owner July 8, 2026 03:55
@zhengweixing zhengweixing changed the title Fix/copilot reasoning thinking fix(agent/copilot): map reasoning deltas to EventThinking Jul 8, 2026
When cc-connect bridges Copilot CLI through the generic ACP adapter
([projects.agent] type = "acp"), Copilot streams its reasoning/thinking
content as session/update frames whose sessionUpdate discriminator is
`agent_thought_chunk` (captured live via ACP probing against
`copilot --acp --stdio`; payload is '{"content":{"type":"text","text":"..."}}',
e.g. "The user needs to check the health status of their PaaS microservices...").

The reasoning whitelist in mapSessionUpdateFallback only listed
"reasoning", "reasoning_chunk", "thinking", "agent_thinking_chunk" — note
`agent_thinking_chunk` (gerund) vs the actually-emitted `agent_thought_chunk`
(past tense). So `agent_thought_chunk` missed the case and fell into the
default branch, which extracts content.text and emits it as EventText.
Because it was never tagged EventThinking, `thinking_messages=false` could
not suppress it, and the reasoning text leaked into the chat reply.

This is the ACP-adapter counterpart to the copilot-adapter fix in chenhg5#1512.
The earlier PR only patched agent/copilot/session.go (assistant.reasoning_delta
-> EventThinking), which is unused when type = "acp"; the production paas-ops
project uses the generic ACP path, so reasoning still leaked there.

Changes:
- mapSessionUpdateFallback: add `agent_thought_chunk` to the reasoning
  discriminator whitelist so it maps to core.EventThinking.
- Add TestMapSessionUpdate_agentThoughtChunk regression test asserting
  `agent_thought_chunk` with content.text -> core.EventThinking.

With this, `thinking_messages=false` (e.g.
CC_CONNECT_DISPLAY_THINKING_MESSAGES=false) correctly suppresses Copilot
reasoning over the ACP path and it no longer appears inline in the reply.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant