SIGN IN SIGN UP

fix(session): fail loudly when the MCP re-prompt cap is exhausted (#435)

When the MCP re-prompt loop hit its 3-round cap and the model was still asking
for a tool call, apfel stripped the pending tool-call JSON and returned whatever
text was left as a successful answer. The caller got finish_reason: stop and no
indication that tool work had been abandoned half-way -- a partial answer
presented as a complete one.

Stripping the raw JSON is correct and stays correct: it must never reach the
user as text (#187, #358). Reporting the remainder as a normal completion is the
part that was wrong.

Both paths now throw ApfelError.toolExecution, which maps to a 500 server_error
-- the right classification, since the model failing to converge is a
server-side failure, not a caller mistake.

Candidate PR #447 fixed only the CLI path, leaving the HTTP path -- the one that
actually returns finish_reason to a client -- unchanged. #452 was taken instead:
it covers both, and it puts the check in ApfelCore as ensureToolLoopCompleted
with the cap as a single shared constant, so the two paths cannot drift apart
again. The local stripToolCallJSON wrapper in Session.swift became unreferenced
and was removed; the ApfelCore implementation and its #358 tests stay.

1107 unit tests pass.

Closes #435

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ccLBbEaVVd4sJyUd5wyhA
A
Arthur Ficial committed
d0984ad877d5d4a22ef70eef52b097fe37d8da8e
Parent: 68a62f2