fix(session): flatten tool history when a request declares no tools
Issue for this PR
Closes #1315
Type of change
- Bug fix
- New feature
- Refactor / code improvement
- Documentation
What does this PR do?
The bug. compaction, title and summary all run with "*": "deny", so they send no
tools array — while summarizing a session's own history, which is full of tool-call and
tool-result parts. The Altimate gateway rejects that shape: its Responses-API namespace
conversion only runs when tools is present, so the history references functions the request
never declares, every provider in its fallback chain fails, and the client sees only:
UnknownError: "Could not get a response from the agent. Please try again later."
It reads like a gateway outage while ordinary turns keep working — because ordinary turns
declare their tools. Reported by several users at the point where a long session compacts.
Scope of the defect. Measured directly against each provider, with an otherwise identical
request:
| Provider | tool history, no tools declared |
|---|---|
openai/gpt-4o-mini | accepts |
anthropic/claude-sonnet-4.5 | accepts |
| Altimate gateway | rejects |
So the root defect is gateway-side, and is tracked separately — the gateway should
synthesize declarations from the tool_calls already present in the history, which would also
fix the VS Code extension and every other client. This PR is the client-side mitigation.
The fix. ProviderTransform.flattenToolParts rewrites tool-call parts into
[tool call: name(args)] and tool-result parts into [tool result: name] plus the output,
merging results into the preceding assistant turn so role alternation is preserved. It is
applied at the single streamText call in session/llm.ts, and only when the request declares
no tools — every tool-declaring request keeps its structured parts untouched.
Flattening runs for all providers rather than branching on one: a request that declares no
tools has no use for structured tool parts either way, and a single code path avoids a
provider-specific branch that would rot as the gateway changes. Happy to narrow it to
altimate-backend if reviewers prefer.
How was this tested?
- 6 unit tests in
test/provider/flatten-tool-parts.test.ts, covering removal, content
preservation, an assistant turn holding only tool calls, an orphaned tool result, and a
no-op on tool-free history. - Mutation-checked: replacing the function body with a passthrough fails 2 of the 6.
- End to end against the live gateway — the real function output was posted to
/agents/v1/chat/completions:
BEFORE fix (tool messages, no tools declared): FAIL -> generic gateway error
AFTER fix (actual flattenToolParts output) : PASS (63 chunks)
bun run typecheckclean (13/13); fullbun test13,659 pass / 0 fail.
Verification
Drive any session to auto-compaction against the Altimate LLM Gateway — it previously failed at
the Compaction step and now produces a summary. title and summary share the same chokepoint.
🤖 Generated with Claude Code
https://claude.ai/code/session_01V4pzHPsqfbTdLMKzMaSYcF
Summary by cubic
Fixes compaction and title requests failing with a generic UnknownError on the Altimate gateway: those agents declare no tools but were sending tool-call history, which the gateway rejects (OpenAI and Anthropic both accept that shape). Requests that declare no tools now flatten their tool history to text before sending.
Bug Fixes
tool-callparts become[tool call: name(args)];tool-resultparts become[tool result: name]plus output text, including results embedded in assistant messages.- Results merge into the preceding assistant turn so role alternation is preserved; the blank-text filter only drops parts the transform itself produced.
- Title generation now sets
toolChoice: "none", preventing tool-stub injection from repopulatingtoolsand bypassing the flattening. - Tool-declaring requests keep structured parts untouched; flattening runs for all providers to avoid a provider-specific branch.
- Covered by 13 unit tests and verified against the live gateway (fails before, passes after).
- The root defect is gateway-side and tracked separately; this PR is the client-side mitigation.
Summary by CodeRabbit
- Bug Fixes
- Improved compatibility with AI providers that do not support tools by converting historical tool interactions into readable conversation text when needed.
- Preserved tool names, arguments, results, media indicators, and conversation order while avoiding unintended empty assistant messages.
- Requests with tool support continue to retain their original tool interactions.
- Improved handling of embedded and structured tool results, including readable text and media indicators.
- Title generation remains tool-free, preventing historical tool interactions from being reintroduced.