fix: adversarial review remediation — release v0.1.5 (rounds 2 + 3)#8
Merged
Conversation
Fixes the following issues identified during review remediation:
1. anthropic.rb: Fix escaped string interpolation in ProviderError message
for malformed tool call JSON. Removed backslash-escaping so actual tool
name and parser error appear instead of literal \#{...} text.
2. anthropic.rb: Replace thread-unsafe @_stream_* instance variables with
method-local variables. All streaming state is now scoped to the
method/block via a process_anthropic_stream_event helper.
3. agent/core.rb + all providers: Thread per-agent config to provider
construction. BaseProvider accepts config: kwarg; providers use it
instead of RubyPi.configuration directly.
4. All providers: Detect HTTP error status in on_data streaming callbacks.
Accumulate error response bodies separately so ApiError contains the
full error body even when streaming consumed the response.
5. compaction.rb: Change compaction summary from role: :user to
role: :assistant to prevent consecutive user messages that Anthropic
rejects (strict user/assistant alternation).
6. openai.rb: Raise ProviderError on nil/blank tool_call_id in tool
result messages and assistant tool calls (same fail-fast as Anthropic)
instead of silently sending 'unknown'.
7. gemini.rb: Parse actual finishReason from streaming response instead
of hardcoding 'stop'.
8. README.md: Fix event key references (e[:iteration] -> e[:turn],
event[:iterations] -> event[:result].turns).
9. CHANGELOG.md: Add comprehensive v0.1.4 entry documenting all fixes.
10. base_provider.rb: Remove dead parse_sse_events method (all providers
now use real incremental streaming via on_data).
11. Gemfile + gemspec: Remove arbitrary < 3.4 upper bound on
faraday-net_http.
12. fallback.rb: Stream events directly to consumer on happy path instead
of buffering. Emit :fallback_start event on primary failure so consumer
can clear partial state before fallback streams.
All 444 tests pass.
Addresses defects identified during round-3 review of the round-2 fixes (commit eace165). All 449 specs pass under Ruby 3.4.9. Critical: - Streaming error_body recovery on Gemini and OpenAI: round-2 added the error_body accumulator on all three providers but only Anthropic passed it to handle_error_response. Gemini and OpenAI now also pass override_body so ApiError#response_body carries the real server message on streaming HTTP failures. - Compaction summary role is now context-aware: round-2 changed the role from :user to :assistant to avoid consecutive user messages, but produced consecutive assistants when the first preserved message was already :assistant. The role is now :user when the next preserved is :assistant, and :assistant otherwise. New compaction specs exercise both orderings. - :fallback_start StreamEvent now propagates to agent users via a new :provider_fallback agent event. The agent loop also clears the streamed-content accumulator on fallback so the recorded response reflects only the fallback's output. Major: - StreamEvent#fallback_start? predicate added for symmetry with text_delta?, tool_call_delta?, done?. - Loop#act guards JSON.generate(result.value) against JSON::GeneratorError / TypeError so tools returning Time, Date, or other non-JSON-native objects no longer abort the agent. Falls back to value.to_s. - Tools::Executor.deep_symbolize_keys promoted to a public class method so the loop can apply it once up front. tool_calls_made arguments now match the symbol-keyed shape the tool block receives, instead of leaking the raw string-keyed JSON.parse output through Result#tool_calls_made. Documentation: - Agent::Core#config: kwarg docstring corrected. The kwarg is informational; per-agent provider config must be passed to the model factory: RubyPi::LLM.model(:openai, "gpt-4o", config: cfg). Round-2 CHANGELOG claimed "now flows through to provider construction" — that was untrue and has been corrected. - CHANGELOG: removed two references to nonexistent BufferedStreamProxy class. Restructured 0.1.4 entry to be accurate; added 0.1.5 entry. - CLAUDE.md: "Adding a New LLM Provider" no longer references the deleted parse_sse_events helper; describes the on_data + override_body pattern. Extension example uses event[:result].turns instead of the nonexistent event[:iterations]. Event list updated. - README: documents :fallback_start StreamEvent and the agent-level :provider_fallback event. Tooling: - .gitignore added (.DS_Store, *.gem, vendor/bundle, coverage). - .ruby-version pins to 3.4.9 (matches CI's 3.4 entry). Version bump: 0.1.3 -> 0.1.5 (skipping 0.1.4 — round-2 work was completed but never tagged or published; the 0.1.5 CHANGELOG entry documents both rounds). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two rounds of adversarial review remediation, shipping as
v0.1.5. All 449 specs pass under Ruby 3.4.9.Round 2 (commit
eace165)Addressed 12 defects from review v2: Anthropic
\#{...}literal interpolation, thread-unsafe@_stream_*ivars in Anthropic streaming, error-body capture inon_data(Anthropic only — see round 3), compaction-poisoning-system-prompt role change, OpenAItool_call_idfail-fast, Gemini streamingfinishReason, README/CLAUDE.md event-key drift, deadparse_sse_eventsremoval,faraday-net_httpcap removal, Fallback no-longer-buffers happy path,:fallback_startevent introduction.Round 3 (commit
b0f750e)Addressed defects in round 2's fixes:
error_bodywas only wired through on Anthropic — Gemini and OpenAI now also passoverride_body:tohandle_error_response, so streaming HTTP failures actually carry the real server message inApiError#response_body.:assistant. Role is now context-aware (:userif next is:assistant, otherwise:assistant). New specs cover both orderings.:fallback_startwas emitted but silently dropped by the agent loop — the loop now translates it into a new agent-level:provider_fallbackevent and clears the streamed-content accumulator.Agent::Core#config:kwarg was decorative — round-2 CHANGELOG claimed it "flows through to provider construction" but it does not. Docstring and CHANGELOG corrected; per-agent config must be passed to the model factory directly.JSON.generate(result.value)could crash on tools returningTime/Date/custom objects. Now wrapped in a rescue withto_sfallback.tool_calls_madearguments leaked the raw string-keyed JSON.parse output while the tool block received symbol keys. Both now use the symbol-keyed form via the newly-publicTools::Executor.deep_symbolize_keys.StreamEvent#fallback_start?predicate.BufferedStreamProxyclass from CHANGELOG.parse_sse_eventsreference and the wrongevent[:iterations]key.:fallback_startand:provider_fallback.Other
.gitignoreadded (.DS_Store,*.gem,vendor/bundle)..ruby-versionpins to3.4.9(matches CI's 3.4 entry).REVIEW.mddocuments the round-3 review findings for traceability.Test plan
bundle exec rspec— 449 examples, 0 failures, ~3 seconds🤖 Generated with Claude Code