Skip to content

v0.9.0

Choose a tag to compare

@WCHungBlake WCHungBlake released this 03 Oct 19:24
· 17 commits to main since this release

Negotiate protocolVersion at init time and emit v1 or v2
session/update payloads accordingly. Same code base, dispatch by
version. The implementation adds:

  • protocol::ProtocolVersion enum (V1, V2, LATEST) and the
    Session::protocol_version field for defense-in-depth and future
    per-session routing.
  • AppState::protocol_version (set by run_acp_loop from the negotiated
    value) so every emit helper branches on the same source of truth.
    AppState: Clone is hand-written; sessions are wrapped in
    Arc<RwLock<...>> so the clone shares the map.
  • acp::notify_*_for(version, …) dispatchers that route to the v1 or
    v2 implementation. The plain (no-suffix) notify_* functions stay
    as the v1 shortcut for code paths that have not migrated.
  • acp::notify_state_idle_for() for v2-only state_update (IdleState).
  • negotiate_protocol_version(params) in main.rs — implements the
    ACP spec's "pick the highest version we both support" rule.
    Clients that omit protocolVersion fall back to v1 (conservative
    default). Clients requesting a version > 2 fall back to v2 with a
    warning.
  • v2-shaped InitializeResponse: unified info + capabilities
    (role-agnostic), with capabilities.session.prompt.image expressed
    as {} (capability marker) when supported.
  • v2-shaped session/update discriminators:
    • agent_message_chunk, agent_thought_chunk (chunk variants)
    • tool_call_update (instead of v1's tool_call)
    • plan_update with plan: { type: "items", planId, entries[] }
  • v2 baseline session/close (alias for v1's session/end) and
    session/list (returns active sessions as
    {sessions: [{sessionId, cwd}], nextCursor: null}).
  • v2 graceful rejects for session/delete, session/resume,
    session/load: -32601 not_implemented / -32001
    no_persistence with stable data.reason.
  • LlmConfig.build_body is now backend-aware: OpenAI-compatible gets
    top-level temperature / max_tokens; Ollama native gets them
    inside options (temperature + num_predict). The previous
    top-level-only shape meant every Ollama native request silently used
    the model's defaults for sampling.

Tests

9 new tests in tests/clients/protocol_version.rs:

  • v2_initialize_returns_unified_capabilities_shape
  • v2_tool_call_uses_tool_call_update_not_tool_call
  • v2_plan_uses_plan_update_with_plan_id
  • v2_emits_state_update_at_end_of_turn
  • v2_does_not_emit_state_update_for_v1_clients
  • v1_client_gets_legacy_shapes_unchanged
  • inspector_style_session_list_returns_empty_array_with_no_sessions
    (updated from the pre-0.9.0 negative-test now that session/list
    is implemented)
  • inspector_style_session_close_succeeds_and_variants (updated
    similarly)

168 tests passing; cargo fmt --check and
cargo clippy --all-targets -- -D warnings clean.

Migration

  • v1 Clients see no behavior change. The plain notify_* functions
    still emit v1, and every emit site in main.rs was migrated to
    use the _for(version, …) helpers that branch on the negotiated
    version.
  • Existing inspector_style_session_list_returns_method_not_found_gracefully
    and inspector_style_session_close_returns_method_not_found_gracefully
    tests were renamed and rewritten as positive tests. The original
    intent of those tests (probe capability, get clear error) is
    preserved — session/close now returns -32001 UnknownSession
    instead of -32601 MethodNotFound when called on a non-existent
    session, and session/list returns {sessions: []} with no
    sessions open.

Co-Authored-By: Claude noreply@anthropic.com

Full Changelog: v0.8.2...v0.9.0