Skip to content

Fix proxy bugs, improve concurrency, and update SDK options - #42

Open
gustavokch wants to merge 36 commits into
RichardAtCT:mainfrom
gustavokch:main
Open

Fix proxy bugs, improve concurrency, and update SDK options#42
gustavokch wants to merge 36 commits into
RichardAtCT:mainfrom
gustavokch:main

Conversation

@gustavokch

Copy link
Copy Markdown

This PR introduces reliability improvements, full support for concurrent SDK calls, wiring of new Claude API options, and resolutions for several critical proxy bugs.

Features & Enhancements

  • SDK Options Wiring: Full support for reasoning_effort, response_format, thinking, max_budget_usd, and user fields passed directly to the Claude SDK.
  • Concurrency: Removed os.environ mutex (_env_lock) by passing auth via options.env, allowing fully concurrent SDK calls. SessionManager has been refactored to use asyncio.Lock with all session methods converted to async.
  • Token & Reason Mapping: Extracts real token counts directly from the SDK's ResultMessage and properly maps stop_reason to finish_reason (e.g., max_tokenslength).
  • Tool Handling: Changed AnthropicMessagesRequest.enable_tools default to False so simple message requests do not trigger unintended 10-turn loops.

Bug Fixes

  • Session Continuity: Fixed session continuation by correcting continue_session to continue_conversation and replaced list appending with replacement to prevent exponential duplication.
  • Timeouts & Hangs: Wrapped async query() iterations with asyncio.timeout to prevent indefinite hangs when the SDK subprocess stalls.
  • Proxy Reliability:
    • Removed filter_content() from user input which was silently stripping XML-like tags.
    • Secured /v1/auth/status endpoint with the verify_api_key() auth guard.
    • Marked the Bash tool as is_safe=False.
    • Replaced bare except: clauses with except Exception:.

Maintenance & Chores

  • Updated poetry.lock and the test suite for compatibility with pydantic 2.13 and poetry 2.3.
  • Replaced deprecated datetime.utcnow() with datetime.now(timezone.utc).
  • Ignored .worktrees directories in .gitignore.
  • Added diagnostic print statements for /v1/messages and improved the test_message.py script.

…condition, and more

- Bug 1: Remove filter_content() from user input (silently stripped XML-like tags)
- Bug 2: Add asyncio.Lock to serialize os.environ mutation under concurrent requests
- Bug 3: Replace session message append with replace (prevent exponential duplication)
- Bug 4: Replace bare except: with except Exception: in 3 locations
- Bug 5: Use __version__ from src/__init__.py instead of hardcoded "1.0.0"
- Bug 7: Add verify_api_key() auth guard to /v1/auth/status endpoint
- Bug 8: Replace deprecated datetime.utcnow() with datetime.now(timezone.utc)
- Bug 9: Fix GitHub URL in landing page (aaronlippold → RichardAtCT)
- Bug 10: Mark Bash tool as is_safe=False
- Bug 11: Use DEFAULT_MODEL constant in debug endpoint example request
- Wrap async query() iteration with asyncio.timeout(self.timeout) to
  prevent indefinite hangs when the SDK subprocess stalls
- Change AnthropicMessagesRequest.enable_tools default to False so
  simple message requests don't trigger bypassPermissions + 10 turns
- Add diagnostic print() statements in /v1/messages handler to surface
  handler entry and run_completion call in server output
- Improve test_message.py: pipe server output to stderr, add
  DEBUG_MODE=true, reduce client timeout from 120s to 60s
PR 1 — Critical bug fixes:
- Fix continue_session → continue_conversation (sessions now actually continue)
- Wire max_thinking_tokens through to SDK via generic setattr approach
- Extract real token counts from SDK ResultMessage usage field
- Map stop_reason to proper finish_reason (max_tokens → length, etc.)

PR 2 — Concurrency & reliability:
- Remove os.environ mutex (_env_lock) — pass auth via options.env instead,
  allowing fully concurrent SDK calls (no more per-worker serialization)
- Replace threading.Lock with asyncio.Lock in SessionManager to avoid
  blocking the event loop; all session methods converted to async

PR 3 — SDK options wiring:
- Refactor run_completion to accept claude_options dict; apply via setattr
- Add reasoning_effort, response_format, thinking, max_budget_usd fields
- Forward user field to SDK
- Bump version to 2.3.0
gustavokch and others added 13 commits March 24, 2026 18:06
Updated repository URL in the README file.
feat: Gemini CLI proxy support and interactive chat client
…ncurrency

Optimize CLI latency and add process concurrency cap
- Optimize prompt generation to only send new messages when resuming sessions
- Remove redundant 'Human:'/'Assistant:' prefixes for Gemini models
- Add prompt echo filtering to response content
- Update interactive chat client to use persistent session IDs
Fix Gemini history echoing and improve session continuity
- Implement content buffering and prompt stripping for Gemini streaming
- Set default max_thinking_tokens to 4000 for Claude 4 models
- Add improved logging for empty response cases
…esponse

Fix streaming echo and Claude response failures
- Support AssistantMessage and ContentBlockDelta message types in streaming
- Enhance MessageAdapter.filter_content to provide conversational fallbacks
- Prevent 'Unable to respond' errors by ensuring content_sent is correctly tracked
- Replace tool tags with placeholders instead of deleting them
- Support both <thinking> and <thought> tags
- Add raw content logging for easier debugging
- Update unit tests for new filtering behavior
…-switch

Fix wrapper session handling for model switches
brandonros added a commit to brandonros/claude-code-openai-wrapper that referenced this pull request Apr 18, 2026
Blended gustavokch/main (PR RichardAtCT#42 on RichardAtCT) into our main, on top of
prodigy-sln's security audit.

Pulled in:
- Gemini CLI proxy support (+src/gemini_cli.py, GEMINI_MODELS)
- Async conversion of SessionManager (asyncio.Lock) for concurrency
- Latency optimizations (parallel prewarming, process semaphore)
- Real SDK token-count and stop_reason extraction
- Fixes for session model switches, streaming echo, history echoing
- Interactive chat client, setup scripts

Conflict resolutions:
- src/constants.py: kept our DEFAULT_CLAUDE_MODELS + CLAUDE_MODELS_OVERRIDE
  env mechanism, extended with gustavokch's fuller 4.x family list and his
  new GEMINI_MODELS block.
- src/session_manager.py: took gustavokch's async rewrite but ported
  prodigy's check_session_limit() to async and kept the max_messages
  Session field.
- src/main.py: kept prodigy's security hardenings — redacted debug logging,
  redact_request_body() for validation errors, minimal /v1/auth/status
  response, and X-Claude-Model-Warning header — while adopting gustavokch's
  real-metadata-based usage/stop_reason extraction. Caller of
  check_session_limit now awaits it.
- tests/test_session_manager_unit.py: prodigy's TestSessionManagerSessionLimit
  and TestCheckSessionLimit tests converted to async + await.
- pyproject.toml: tightened claude-agent-sdk to ^0.1.63.
- poetry.lock: regenerated against new constraint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gustavo Mendes and others added 7 commits August 10, 2026 20:04
…rough

Brainstormed design covering:
- Merge 2 missing upstream commits from RichardAtCT (dynamic model list + v2.3.0 release)
- Upgrade claude-agent-sdk 0.1.18 -> 0.2.134 with typed-message parser hardening
- GLM-5.2 passthrough via Claude Code (advertise in /v1/models + document setup)

Target version reality: 2.1.266 is unpublished on npm; current is CLI 2.1.226
(installed) + SDK 0.2.134.

Co-Authored-By: Claude <noreply@anthropic.com>
Six-task TDD plan: merge upstream/main, bump claude-agent-sdk 0.1.18->0.2.134,
harden the message parser with typed isinstance dispatch, advertise glm-5.2
passthrough, update docs, final verification.

Co-Authored-By: Claude <noreply@anthropic.com>
- Add pythonpath=["."] so test modules using 'from tests.conftest import ...'
  collect without PYTHONPATH (16 collection errors otherwise).
- Filter the anyio 'return inside finally' SyntaxWarning and asyncio event-loop
  DeprecationWarnings surfaced by deps on Python 3.14 (44k -> 32 warnings).

Co-Authored-By: Claude <noreply@anthropic.com>
Remove the fabricated conversational fallback ('I've processed your
request...') that was returned whenever no assistant text was extracted.
parse_claude_message now returns None, matching its contract and what
callers already expect. All four call sites in main.py (928, 1148, 1313,
1485) already handle a falsy/None return safely.

Co-Authored-By: Claude <noreply@anthropic.com>
The fork deliberately routes permission_mode (and model, max_turns,
allowed_tools, etc.) through a generic claude_options dict rather than a
dedicated parameter. Rewrite the stale signature assertion to lock in
that contract, and add a behavioral unit test that mocks
claude_agent_sdk.query and proves permission_mode supplied via
claude_options reaches ClaudeAgentOptions.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Gustavo Mendes and others added 9 commits August 10, 2026 21:01
Replace the dir()-walk object->dict conversion with explicit checks against
AssistantMessage/ResultMessage/SystemMessage so SDK 0.2.x field shapes are
handled reliably. Unknown types fall back to attribute copy.

Co-Authored-By: Claude <noreply@anthropic.com>
The .type attribute check no longer fires on SDK 0.2.x typed messages.

Co-Authored-By: Claude <noreply@anthropic.com>
GLM-5.2 is served through Claude Code via ANTHROPIC_BASE_URL. Add GLM_MODELS,
append passthrough models (GLM + Gemini) at the /v1/models edge so they are
discoverable even though they never appear in Anthropic's live Models API.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
… fix model example

Address final-review findings: add duration_ms/usage asserts to the parser
test, remove the brittle negative permission_mode signature assertion, and
update an outdated model example in README.

Co-Authored-By: Claude <noreply@anthropic.com>
…el fetch

- Add CLAUDE_CLI_PATH (default /Users/gus/.local/bin/claude, env-overridable)
  and pass it as cli_path to ClaudeAgentOptions in both verify_cli and
  _run_completion_inner, so the wrapper deterministically uses the local
  native Claude Code install.
- Extend GLM_MODELS with glm-5.2[1m] (1M-context variant); advertised in
  /v1/models via PASSTHROUGH_MODELS.
- parse_claude_message: skip result messages with is_error=True so upstream
  API errors (429/500/529, subtype="success" but is_error=True) are not
  returned to the client as a normal 200 assistant reply.
- constants: parse MODEL_LIST_CACHE_TTL_SECONDS / MODEL_LIST_ERROR_TTL_SECONDS /
  MODEL_LIST_REQUEST_TIMEOUT_SECONDS through _env_int/_env_float so a stray
  NAME= in .env does not raise ValueError at import and stop the server.
- main: cap _fetch_anthropic_models pagination at max_pages=100 so a
  misbehaving upstream returning has_more=true with a stagnant last_id
  cannot loop forever while holding _model_list_lock.
- main: replace _pick_latest_sonnet's lexicographic id tiebreak with a
  numeric _version_key so claude-sonnet-4-10 ranks above claude-sonnet-4-9.
- main: widen _iso_to_unix except to (ValueError, OverflowError, OSError) so
  a far-future created_at on 32-bit/Windows does not discard the model page.
- tests: route _model_list_cache and constants.RESOLVED_DEFAULT_MODEL
  mutations through monkeypatch to stop order-dependent cross-test leaks;
  assert glm-5.2[1m] is listed; assert cli_path reaches ClaudeAgentOptions.

Co-Authored-By: Claude <noreply@anthropic.com>
- parameter_validator: include GLM_MODELS in SUPPORTED_MODELS so glm-5.2
  (which routes through claude_cli) no longer logs a spurious "not in the
  known supported models list" warning. Gemini stays out — it routes to
  gemini_cli and skips validation.
- claude_cli: route passthrough (non-Claude) models to a neutral system
  prompt instead of the claude_code preset. The agentic preset primed
  glm-5.2 to emit tool_use on turn 1, which the max_turns cap surfaced as
  "Reached maximum number of turns (1)". Claude models keep the preset;
  explicit caller system prompts always win.
- main: stop forcing max_turns=1 on the tools-disabled path (4 sites).
  With all tools disallowed there is nothing to execute, so the SDK default
  (10) gives a stray passthrough tool_use room to fall back to text instead
  of hard-erroring.
- tests: assert GLM in SUPPORTED_MODELS; assert passthrough->neutral prompt,
  Claude->preset, explicit-prompt-wins routing.

Verified live: POST /v1/chat/completions with glm-5.2 returns text with
finish_reason "stop" (previously errored "Reached maximum number of turns (1)").

Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: the wrapper passed system_prompt as {"type":"text","text":...},
which the SDK's CLI flag builder does not recognize. It emitted no
--system-prompt flag, so the CLI ran its default — the full claude_code
agentic prompt — on every request. Caller-supplied system messages were
silently dropped. On top of that, tools defaulted to the full Claude Code
tool set (schemas sent to the model) and setting_sources defaulted to
["user","project"], injecting the server's CLAUDE.md / memory.

Fixes:
- claude_cli: pass system_prompt as a plain str so the SDK emits
  --system-prompt <str>, which replaces the CLI default. The claude_code
  default is retained for Claude models (preset dict without "append" is a
  recognized no-op that leaves the default in place); passthrough models
  keep the neutral prompt. Documented the SDK flag contract inline.
- main: on the tools-disabled path (4 sites) set tools=[] (no tool schemas
  sent) and setting_sources=[] (skip CLAUDE.md / memory injection).

Verified live against the GLM proxy (z.ai), model glm-5.2, prompt "hi":
input_tokens 18549 -> 105 (-99.4%).

Behavior change: server CLAUDE.md / memory is no longer injected into
no-tools chat Q&A. The tools-enabled (agentic) path is unchanged.

Tests: 480 passed, 1 skipped. Updated the test encoding the old dict form;
added TestSystemPromptFlagContract (4 tests locking the SDK flag behavior)
and aligned the 3 routing tests to the str form.

Co-Authored-By: Claude <noreply@anthropic.com>
Sync upstream + upgrade SDK to 0.2.134+ + GLM-5.2 passthrough
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