Skip to content

abridge: SessionForward + Convert∘Session composition (PR #122 stage A)#133

Merged
Meirtz merged 1 commit into
masterfrom
abridge/session-forward
Jul 2, 2026
Merged

abridge: SessionForward + Convert∘Session composition (PR #122 stage A)#133
Meirtz merged 1 commit into
masterfrom
abridge/session-forward

Conversation

@Meirtz

@Meirtz Meirtz commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

First slice of the #122 split (see the coordination comment there for the full stage map). Ports the abridge SessionForward / AnthropicToOpenAI work from origin/abridge/gateway-tito onto master, plus fixes found in the pre-PR adversarial review.

What's in

SessionForward — forward to session-scoped sidecars (the TITO gateway shape): creates the session lazily on first request (or eagerly via open()), rewrites every inbound path to /sessions/{id}{path}, delete_session() reaps. Assigning session_id attaches to an existing session (no create call).

AnthropicToOpenAI — Anthropic Messages agent over ANY OpenAI-compatible downstream Handler. Pure protocol translation, transport-blind, SDK-free; remembers each tool-calling assistant verbatim and replays it on later turns so a session-recording backend byte-matches history.

Forward.handler() — the composition seam: a bound Handler a wrapper can sit on without knowing the transport. Carries aclose() so Proxy.stop reaps the forwarder's pool through the composition:

tito  = SessionForward(gateway_url, paths=["/v1/chat/completions"])
proxy = Proxy(AnthropicToOpenAI(tito.handler(), model="qwen3-4b"))
# agent speaks Anthropic; the session lives in the SessionForward

proxy — typed DynamicRoutes protocol for construction-time routes (replaces the duck-typed seam); the tunnel 400s on non-object JSON bodies instead of silently coercing to {}.

sio (core, small)RemoteSioError now carries the error envelope's status_code, so the tunnel replies with the handler's real status (429/400/503) instead of collapsing everything to 502. The source branch had this producer half; the initial port took only the consumer — caught by the adversarial review.

Review fixes on top of the port

  • _ensure_session accepts any 2xx create response, not just 200
  • session_id setter attach semantics (assigned id no longer silently overwritten by a fresh create)
  • aclose() delegation chain: Proxy(AnthropicToOpenAI(fwd.handler())) no longer leaks the httpx pool past Proxy.stop
  • Proxy.session() detects body failure by catching it, not sys.exc_info() — teardown errors aren't swallowed for callers inside an unrelated except block
  • delete_session() after close uses a one-shot client instead of resurrecting an unowned pool
  • Assistant replay keys on (id, name, canonical args) per tool call — servers that reuse tool-call ids across turns (TGI's literal "0") no longer corrupt replayed history

Deliberately kept from master: anthropic_from_openai.py + test_adapter_anthropic.py (they carry #124's aclose work).

Verification

  • uv run pytest tests/ plugins/ -q — 422 passed (13 new tests: session lifecycle, attach, 2xx, aclose chain, tunnel status propagation, teardown-in-except, pool reap, replay id-reuse)
  • uv run ruff check . / uv run pyright — clean / 0 errors
  • Pre-PR adversarial review workflow (3 perspectives + 2 rebutters per finding): 16 raw findings → 7 confirmed, all fixed here or triaged; 7 refuted

🤖 Generated with Claude Code

Port the abridge SessionForward/AnthropicToOpenAI work from
origin/abridge/gateway-tito onto master as the first slice of the #122
split:

- SessionForward: forward to session-scoped sidecars (create-on-first-
  request, path rewrite to /sessions/{id}{path}, delete_session reap).
- AnthropicToOpenAI: Anthropic Messages agent over any OpenAI-compatible
  downstream Handler — protocol translation only, transport-blind, with
  verbatim assistant replay so session recorders byte-match history.
- Forward.handler(): the composition seam — a bound Handler a wrapper
  can sit on without knowing the transport.
- proxy: typed DynamicRoutes protocol for construction-time routes;
  tunnel 400s on non-object JSON bodies instead of coercing to {}.
- sio: RemoteSioError carries the error envelope's status_code, so the
  tunnel replies with the handler's real status (429/400/503) instead
  of collapsing every remote failure to 502. (The source branch had
  this producer half; the port initially took only the consumer.)

Review fixes on top of the port (from the pre-PR adversarial review):

- _ensure_session accepts any 2xx create response, not just 200.
- session_id setter now attaches: assigning an id marks the session
  ready so the first request doesn't create a fresh session and
  silently overwrite the assigned id.
- Forward.handler() returns a closeable handler and AnthropicToOpenAI
  delegates aclose() to it, so Proxy.stop reaps the forwarder's httpx
  pool through the converter composition.
- Proxy.session() detects a body failure by catching it, not by probing
  sys.exc_info() — teardown errors are no longer swallowed for callers
  that open the session inside an unrelated except block.
- delete_session() no longer resurrects a closed pool for one DELETE;
  it uses a one-shot client, so the documented stop → harvest → reap
  flow leaks nothing.
- Assistant replay memory keys on (id, name, canonical args) per tool
  call, not ids alone — servers that reuse tool-call ids across turns
  (TGI's literal "0") no longer corrupt the replayed history.

Co-authored-by: FatPigeorz <wjhhhhhha0@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.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