feat(providers): openclaude provider routing + resilience (resume, fallback, reconnect)#108
Conversation
… mode Non-Anthropic providers in the agent chat previously bypassed openclaude entirely — a raw /chat/completions fetch with no tool calling, no session history, and a hard API-key requirement that broke Codex OAuth. The terminal was separately blocked by the isCodeModel() name heuristic for agentic models like openrouter/owl-alpha. - chat-bridge: external providers in code mode now go through the Agent SDK with pathToClaudeCodeExecutable pointing at the openclaude binary and a clean whitelisted env (mirrors ClaudeBridge). Restores tool calling, structured streaming, UI tool approval, and session resume. Chat-only models keep the REST path; missing binary falls back with a clear log. - chat-bridge: external providers get full system-prompt replacement for agent personas (append is too weak for GPT models), matching the terminal behavior. - provider-config: new per-provider `mode: code|chat` field overrides the model-name heuristic; the terminal no longer refuses agentic models whose names don't match the code regex. - providers.example.json: mode field on openrouter/omnirouter + new NVIDIA NIM provider entry (OpenAI-compatible endpoint). - tests: provider-config mode override coverage (node --test). Verified end-to-end against OpenRouter: text streaming and Read tool call complete with all UI events (tool_use_start, tool_input_delta, block_stop, result). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OpenClaude >=0.18 detects integrate.api.nvidia.com and demands the key
in NVIDIA_API_KEY, exiting 1 when only OPENAI_API_KEY is set ("Claude
Code process exited with code 1" in the chat). Derive NVIDIA_API_KEY
from OPENAI_API_KEY in loadProviderConfig so the UI keeps a single key
field, and allowlist the var in both config layers.
Verified: chat session via Agent SDK + openclaude against NVIDIA NIM
(stepfun-ai/step-3.7-flash) completes with result: success.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extends the NVIDIA provider beyond chat/code into image generation, and fixes three bugs found while dogfooding the multi-provider flow: - ai-image-creator: NVIDIA NIM provider with FLUX models — flux.2-klein-4b (default, best composition, ~2.5s), flux.1-dev (30 steps), flux.1-schnell. Aspect-ratio mapping constrained to the dimension set and 1.06MP pixel budget the API validates; per-model cfg_scale/steps defaults (schnell requires cfg=0, klein requires cfg>=1); JPEG output auto-converted to PNG via ImageMagick with ffmpeg fallback (temp files beside the output — snap-confined ffmpeg cannot read /tmp). Uses NVIDIA_API_KEY from .env. - backend/providers: saving provider config with a blank key field no longer wipes the stored secret — the UI submits empty password inputs when editing other fields (e.g. model), silently erasing the API key and breaking every session afterwards with auth errors. - claude-bridge: terminal agent personas now resolve from WORKSPACE_ROOT with cwd fallback, matching chat-bridge (cab8966) — sessions started outside the workspace root were silently falling back to a generic "You are the X agent" persona. - Makefile: `make telegram` fails loudly when screen/bun are missing instead of printing a false success message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…le providers Enforce a minimum interval between chat completion requests and retry 429/503 responses with exponential backoff, honoring Retry-After. Configurable via CHAT_MIN_INTERVAL_MS, CHAT_MAX_RETRIES, CHAT_BASE_DELAY_MS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
onclose only cleared the keepalive and never reconnected — a dropped socket left the terminal/chat dead until the component remounted (switching tabs). Now both components reconnect with capped exponential backoff and rejoin immediately on visibilitychange, replaying the session buffer on a cleared terminal to avoid duplicate output. If the process died while disconnected, surface it instead of silently restarting the agent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r fallback - Pin the CLI conversation to the terminal-server session UUID: first start uses --session-id, restarts use --resume when a persisted conversation file exists — provider crashes and server restarts no longer lose the conversation. - Per-agent model tiers: agents declare model: opus|sonnet|haiku in frontmatter; providers.json maps each tier to a provider model via the new model_tiers field. - Pass --fallback-model from the new fallback_models chain (first entry distinct from the primary). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… totals Older skill versions logged image cost entries without token_usage.total_tokens — the unguarded access in the Image Generation table threw a TypeError and unmounted the whole page. Normalize the image-costs payload defensively, same pattern as normalizeCostData. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eator New 'openai' provider hitting api.openai.com/v1/images/generations directly, with the 'image2' model keyword (auto-detects provider). Requires a platform API key (AI_IMG_CREATOR_OPENAI_KEY or OPENAI_API_KEY) — ChatGPT Plus/Codex OAuth tokens lack the api.model.images.request scope (verified: 401), so they cannot be used for image generation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ude 0.18 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e cost estimate Images API bills text input and image output at different per-token rates — use the prompt/completion split when available instead of total_tokens at the input rate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dation - Heartbeats can now run an in-process Python handler instead of spawning a Claude agent: new 'handler' field (max_turns=0), DB migration, schema validation, dispatcher sync, runner execution path. - provider_fallback.py: 429/quota detection + model/provider rotation with cooldown tracking (foundation; not yet wired into the runner). Tests: tests/heartbeats 25/26 pass (the 1 failure is a data-driven seed assertion against the gitignored local heartbeats.yaml, not a regression). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @sistemabritto, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
step7 now routes through provider_fallback.invoke_with_fallback so a 429/quota error rotates model→provider (NVIDIA chain → Codex → native claude) instead of failing the run. Disable with HEARTBEAT_PROVIDER_FALLBACK=0; unavailable engine falls back to the native claude path (preserved as _step7_invoke_claude_native). Also in provider_fallback.py: - fix NameError on 429 (DEFAULT_COOLDOWN → DEFAULT_COOLDOWN_SECONDS) - align default NVIDIA model chain with validated models; drop OpenRouter (stealth models 404 intermittently) - derive NVIDIA_API_KEY for NVIDIA base URLs (openclaude ≥0.18 needs it) - parse token usage + cost from the CLI JSON envelope so heartbeat runs land real numbers on /costs instead of nulls Tested: invoke_with_fallback + step7_invoke_claude run end-to-end via NVIDIA glm-5.1; tests/heartbeats 25/26 (the 1 failure is the pre-existing data-driven seed assertion on the gitignored local heartbeats.yaml). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| cmd = [magick_cmd, str(jpg_tmp), str(png_tmp)] | ||
| else: | ||
| cmd = ["ffmpeg", "-y", "-i", str(jpg_tmp), str(png_tmp)] | ||
| conv = subprocess.run(cmd, capture_output=True, text=True, timeout=60) |
There was a problem hiding this comment.
security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
Source: opengrep
| proc = subprocess.Popen( | ||
| cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | ||
| text=True, cwd=str(WORKSPACE), start_new_session=True, env=run_env, | ||
| ) |
There was a problem hiding this comment.
security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
Source: opengrep
Summary
Hardens the OpenClaude/OpenAI-compatible provider routing and fixes several reliability issues surfaced while running EvoNexus against non-Anthropic providers (NVIDIA NIM, OpenRouter, Codex OAuth).
Supersedes #106 — same provider-routing foundation, but rebased clean on
mainand scoped to broadly-useful changes only (no workspace-specific skills).What's included
Provider routing & resilience
feat(providers): chat tool-calling via openclaude + explicit provider modefix(providers): deriveNVIDIA_API_KEYfor NVIDIA NIM base URLsfeat(providers): NVIDIA NIM image generation + hardeningfeat(terminal): conversation resume (--session-id/--resumepinned to the terminal-server session UUID) so a provider crash or server restart no longer loses the conversation; per-tier agent models (model_tiersmaps agentopus|sonnet|haikufrontmatter to provider models); and--fallback-modelfrom a configurablefallback_modelschainfeat(chat): rate-limited fetch with retry/backoff (429/503, honorsRetry-After) for OpenAI-compatible providersReliability fixes
fix(dashboard): auto-reconnect agent terminal/chat WebSocket with capped backoff + reconnect-on-visibility (a dropped socket left the terminal dead until remount)fix(dashboard):/costspage crash on image cost entries missingtoken_usage.total_tokensBackend foundation
feat(heartbeats): in-process Python handlers (handlerfield,max_turns=0) as an alternative to spawning a Claude agent +provider_fallback.pyengine (429/quota detection, model/provider rotation with cooldown)Other
feat(skills): OpenAI Images API provider (gpt-image-2) in the sharedai-image-creatorskill, with split-token cost estimationchore(deps): syncuv.lock;docs: codexplan → gpt-5.5 mapping noteTesting
tsc -b+vite buildgreen; no new lint errorstests/heartbeats25/26 pass (the 1 failure is a data-driven assertion against a gitignored localheartbeats.yaml, not a code regression)provider_fallback.pyimports clean; the engine is foundation and not yet wired into the runner🤖 Generated with Claude Code