Skip to content

Releases: lpalbou/AIRelays

AIRelays v0.14.1

Choose a tag to compare

@github-actions github-actions released this 24 Sep 22:58

Added

  • One-line installers. scripts/install-desktop.sh (macOS Apple Silicon, Linux x86_64) and scripts/install-desktop.ps1 (Windows x64) install or update the desktop app from the newest GitHub Release, verify its published SHA-256 digest, and need no sudo, admin rights, Python, or Node. scripts/install-headless.sh installs the airelays relay and CLI from PyPI into an isolated environment (uv or a Python 3.11+ virtualenv). All three accept AIRELAYS_VERSION to pin a release. See the README's Install section.

Fixed

  • The desktop tray icon pulses within about a quarter second of each served request, and bursts stay visible until a second after the last one. Activity polling uses a lightweight GET /v1/relay/status?activity_only=true that requires the same authentication as full status but does not count as a request, consume rate-limit quota, or occupy a concurrency slot.

Changed

  • Disclaimer and FAQ wording on intended use and provider terms were clarified.

AIRelays v0.14.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 10:37

Added

  • Automatic traffic-log retention: 7 days and 1 GiB total by default, with hourly/50 MiB rotation. Cleanup runs at startup and while idle. Oversized records are explicitly marked as omitted, and storage failures are reported without failing relay requests.
  • Live GET/PUT /v1/relay/logging, offline-capable airelays logs, and tray Settings controls with week/month presets, custom limits, usage, and cleanup errors. The shared saved policy survives desktop config regeneration and requires no restart.

Upgrade notes

  • Retention applies to existing traffic logs on the first upgraded start. Eligible files are permanently deleted; archive any history you need beforehand. Console output, uploads and stored conversations are outside this policy.

AIRelays v0.13.1

Choose a tag to compare

@github-actions github-actions released this 05 Sep 21:59

Fixed

  • OpenAI models are the union of enrolled account catalogs. Requests, conversation affinity, and failover use only the subset listing the requested model, including during cooldowns. A failed primary catalog no longer hides other accounts' models.
  • The Models tab reports account coverage and upstream-hidden catalog entries without guessing their underlying model identity.
  • Claude usage includes modern scoped limits such as Fable's weekly allowance, independently of all-model limits, plus usage-credit status and correctly scaled monetary details.
  • Usage snapshots retain their fetch timestamps; Overview refreshes every five minutes and updates reset countdowns from absolute times. Unknown and expired percentages are not displayed as zero usage.
  • OpenAI usage preserves model availability and distinguishes available limit-reset credits from credits usable right now.

AIRelays v0.12.5

Choose a tag to compare

@github-actions github-actions released this 19 Aug 04:25

Fixed

  • An OpenAI account whose stored sign-in was invalidated upstream (refresh_token_invalidated) no longer shows a "Ready" badge next to a bare "Usage unavailable" note in the desktop accounts card. The usage probe's verdict now outranks the credentials-on-disk readiness flags: the badge reads "Sign-in expired", the note says what happened, and the badge sits next to a one-click "Sign in again" button that launches the standard OpenAI sign-in — the relay refreshes the matching account slot in place, so no sign-out is needed first. The raw upstream error stays in the tooltip.

Changed

  • GET /v1/subscription/status?all_accounts=true now always answers the list shape, one entry per account, with a per-account error when a probe fails. Previously a lone enrolled account folded to the bare single-account shape, so its probe failure became a whole-request 503 and single-account installs had no way to see why usage was missing (found by adversarial review). Callers that request all_accounts=true with one enrolled account now receive {"object": "subscription_status_list", "accounts": [...]} instead of the bare status object.

Added

  • The desktop accounts card now surfaces everything the normalized usage payload reports: the code-review quota renders as its own labeled bar (it was previously dropped), bar details carry the absolute reset time next to the countdown, and the per-account "more" panel lists per-window reset timestamps, credits, spend control, limit-reset credits, and the snapshot time above the existing per-model token table. The panel flips downward when there is no room above the row.

AIRelays v0.12.4

Choose a tag to compare

@github-actions github-actions released this 09 Aug 19:27

Fixed

  • Multi-account OpenAI model admission no longer serves a stale /v1/models snapshot after the enrolled account set changes. The local model-catalog cache key now follows the current OpenAI account pool, so adding, removing, or reauthenticating a secondary account invalidates the cached catalog before AIRelays decides whether to forward a model id upstream.
  • Added a regression test for the account-pool topology change case, covering the failure mode where a model was valid for one account set, then should become locally rejected after a second authenticated account narrows the shared model intersection.

AIRelays v0.12.2

Choose a tag to compare

@github-actions github-actions released this 09 Aug 18:48

Fixed

  • Cursor custom-endpoint compatibility on /v1/chat/completions. AIRelays now accepts both malformed Cursor request families publicly reported in 2026: full Responses-style bodies sent to the chat route, and flat Responses-style custom tools / tool choices / assistant tool calls such as ApplyPatch. These requests are normalized locally, sent upstream in the canonical Responses shape, and upstream custom_tool_call items are translated back into chat-completions tool_calls on both streaming and non-streaming responses. Tool-only streamed turns now emit the initial role: "assistant" chunk that strict chat-stream consumers expect.
  • Chat tool-route hardening. /no-tools/v1/chat/completions now rejects non-none tool_choice values instead of forwarding them upstream, and role:"tool" messages must reference a preceding assistant tool call in the same request. AIRelays no longer guesses a function-tool output type for orphaned tool results; malformed transcripts fail loudly instead of silently shifting semantics.
  • Cursor-facing docs now state exactly what AIRelays normalizes on the chat route, how to verify it in traffic logs, and which malformed payloads are still rejected by design.

AIRelays v0.12.1

Choose a tag to compare

@github-actions github-actions released this 01 Aug 16:51

Fixed

  • Deterministic upstream client errors no longer trigger account rotation, benching, or backoff retries. An in-stream invalid_request_error (e.g. "Your input exceeds the context window of this model") was previously classified with everything non-quota as a retriable 502, so one unacceptable request could be retried across every account and several backoff rounds — multiplying paid upstream calls — before answering with a misleading account-limits error. Stream failures are now classified once, in failure_backend_error, into three classes: quota/rate-limit vocabulary → 429 (bench + failover, unchanged); invalid_request_error type, request-shape codes, or a param-bearing error → 400 surfaced immediately with the upstream type/code/message/param passed through verbatim, after exactly one upstream call; anything unknown → 502 (short bench + failover + retry, deliberately kept for genuine upstream bad windows). All OpenAI lanes (non-streaming collect, streaming chat/completions, /v1/responses passthrough) share that single classifier.
  • "All N OpenAI accounts are at their limits (earliest retry in Xs)" is now only claimed when every account is benched by limit-class evidence (an explicit quota rejection or the upstream usage report). Benches carry their evidence kind (quota / auth / transient); rounds of transient 5xx failures answer "All N OpenAI accounts failed for this request" with the last upstream error, and resets_in_seconds is only advertised on all-quota rounds, where the horizon is authoritative. Account statuses expose the bench kind as limited_kind.
  • The traffic log no longer scrubs upstream error codes. The blanket code redaction (meant for OAuth authorization codes) also hit error.code inside error objects, logging "code": "[REDACTED]" where the diagnostic vocabulary belongs. Redaction of code is now scoped: kept readable inside error objects (under an error key, or alongside a message sibling), still scrubbed everywhere else (OAuth callbacks, token exchanges). Client-visible response bodies were never affected — redaction always ran at log-serialization time only.

AIRelays v0.12.0

Choose a tag to compare

@github-actions github-actions released this 01 Aug 15:37

Added

  • Automatic retry with exponential backoff for failed upstream OpenAI calls. A failed call is retried 3 times by default, waiting 5s, 20s, then 60s, and each retry re-runs the full account-pool failover pass, so a transient upstream disruption resolves without the client seeing an error; a request that keeps failing returns the real error. Configure with retry_attempts and retry_backoff_seconds ([providers.openai]; env AIRELAYS_OPENAI_RETRY_ATTEMPTS / AIRELAYS_OPENAI_RETRY_BACKOFF_SECONDS; desktop Settings → Providers). 0 disables retrying, and a schedule shorter than the attempt count repeats its last delay. Retries run only while no response byte has reached the client — non-streaming requests and the pre-header phase of streaming ones. Retries that cannot succeed are skipped: a quota error whose reset lies beyond the remaining backoff budget returns immediately, and a disconnected client stops the loop. Retries appear in the traffic log as retry_backoff records; deliberately skipped retries as retry_skipped.

Fixed

  • Upstream failures surface as OpenAI-shaped errors instead of empty successes. A ChatGPT-backend stream that ends in response.failed/error events, or closes without completing, previously produced an HTTP 200 with content: null and no usage on the non-streaming routes; it now returns {"error": {...}} JSON with the real HTTP status and the upstream's own code and message — 429 for quota errors (including resets_in_seconds, and the earliest account recovery when every enrolled account is at its limit), 502 otherwise. Clients reading FastAPI's default envelope keep working: the body carries a detail string alongside the error object.
  • Streaming failures are signaled, not truncated. On /v1/chat/completions and /v1/completions, a failure after streaming started emits an OpenAI-style in-band data: {"error": ...} event and ends the stream without [DONE]; a stream that ends with no terminal event emits an incomplete_stream error event; response.incomplete terminals finish with finish_reason: "length" and [DONE]. On /v1/responses, upstream failure events pass through verbatim and transport failures surface as an in-band error event. All three streaming lanes contact the upstream before committing SSE headers, so a stream that fails before its first event returns a real HTTP status and participates in automatic retry.
  • The account pool benches and fails over on in-stream failure events for streaming requests, matching the non-streaming path. Events that precede model output are buffered until the stream proves healthy, so a stream that fails before any content moves to the next account transparently; conversation stickiness only pins to accounts that actually delivered content, and requests skip benched or usage-maxed accounts outright. Failover and retry both stop once content has reached the client. The pool also releases the upstream HTTP stream deterministically when a consumer stops mid-stream.
  • The traffic log records why upstream calls fail: response.failed/error SSE events are written as upstream_stream_error records with the upstream error payload, and upstream_usage records (plus the account token tally) cover response.incomplete terminals, which bill real usage.

AIRelays v0.11.0

Choose a tag to compare

@github-actions github-actions released this 28 Jul 19:01

Changed

  • Multi-account balancing now compares accounts on their longest usage window (the weekly budget) instead of the payload's primary slot. The upstream usage payload is plan-dependent — some plans report only a weekly window while others keep a 5h window plus a weekly one — so the primary slot no longer identifies a horizon: comparing it across accounts mixed a weekly percentage against a 5h percentage and could route sustained traffic into a small plan's scarce weekly budget while a large plan idled. Windows are now identified by duration (limit_window_seconds), the balanced strategy equalizes weekly consumption as a percentage of each plan's own capacity, and short-window exhaustion remains covered by proactive benching and failover. Benching still considers every reported window.
  • The per-account "more" token breakdown (desktop Accounts card, window_tokens on the account status payload) is now scoped to the account's longest usage window instead of the 5h bucket, so it accumulates over the weekly budget rather than clearing every few hours. The payload carries the window identity (window_label, window_seconds; scope is now current_usage_window_via_this_relay), and the panel title names the window it covers (e.g. "This weekly window, via this relay"). On upgrade, breakdowns previously anchored to a 5h bucket start fresh once; existing fields (models, totals) are unchanged.
  • Account cards render only the usage windows the upstream actually reports. The synthesized idle "5h window · 0% used · starts with the next request" row is gone: plans without a 5h window no longer show a fabricated one.

Fixed

  • pytest from the repository root now collects only the real test suite (testpaths), instead of crawling into gitignored desktop runtime bundles whose vendored packages fail collection and shadow src/airelay.

Added

  • Structured outputs on Claude chat completions. response_format.type=json_schema and json_object on claude:* models now map to the claude CLI's native --json-schema enforcement (json_object enforces the permissive {"type": "object"} schema), instead of returning a 422. The response content is the enforced JSON only — the model's surrounding prose never reaches a client that asked for JSON — and on streaming requests the JSON text streams as the content deltas (the CLI's StructuredOutput fragments), with a result-envelope fallback if no fragments arrive. Enforcement runs as an internal tool turn upstream, so schema-enforced calls bill some additional output tokens. Unsupported response_format shapes are still rejected loudly, including on /v1/completions, where the parameter does not exist in the OpenAI API and silently ignoring it would hand unenforced text to a client that asked for JSON.
  • /v1/models now publishes each model's structured-output support under airelays.structured_output (parameter, supported types): json_schema + json_object for Claude models, json_schema for OpenAI models (translated for the subscription backend, as before). The desktop Models tab and airelays models advertise it alongside the reasoning modes.

Fixed

  • Streaming requests no longer swallow errors (found by adversarial review). Request validation used to run inside the response generator, after the SSE headers were committed, so an invalid reasoning_effort or response_format on a stream: true request produced an empty 200 stream instead of an error. Claude streaming requests now validate before headers and return real 4xx status codes; both OpenAI streaming routes now contact the upstream and await its first event before committing headers, so upstream rejections surface as real status codes too. Failures that happen after streaming has started (provider timeouts, CLI failures) now emit an OpenAI-style in-band data: {"error": ...} event instead of silently truncating the stream — including the CLI's exit-0 error envelopes, which the streaming paths previously narrated as assistant content.
  • reasoning_effort is now honored on /v1/completions for OpenAI models (it was silently dropped there while chat completions and the Claude runtime honored it), and an explicit JSON null effort is treated as absent on both routes instead of being forwarded as reasoning: {"effort": null}.
  • Structured-output honesty hardening (found by adversarial review): a schema-enforced run that produces no schema-conforming output now fails loudly instead of serving prose or an upstream error message under the JSON contract; a response_format without a type is rejected like OpenAI does instead of silently degrading to unenforced text; oversized schemas (200 KB serialized cap; they travel on the local CLI's argv) and NaN/Infinity values get clean 422s instead of raw OS errors; and if a future CLI ever streamed more than one structured-output block, only the first is forwarded rather than concatenating into unparseable JSON.
  • The Claude runtime documentation no longer claims structured outputs are unsupported; docs/api.md, README, troubleshooting, and the LLM-readable indexes were aligned with the verified behavior above.

AIRelays v0.10.0

Choose a tag to compare

@github-actions github-actions released this 16 Jul 09:54

Added

  • Structured outputs on Claude chat completions. response_format.type=json_schema and json_object on claude:* models now map to the claude CLI's native --json-schema enforcement (json_object enforces the permissive {"type": "object"} schema), instead of returning a 422. The response content is the enforced JSON only — the model's surrounding prose never reaches a client that asked for JSON — and on streaming requests the JSON text streams as the content deltas (the CLI's StructuredOutput fragments), with a result-envelope fallback if no fragments arrive. Enforcement runs as an internal tool turn upstream, so schema-enforced calls bill some additional output tokens. Unsupported response_format shapes are still rejected loudly, including on /v1/completions, where the parameter does not exist in the OpenAI API and silently ignoring it would hand unenforced text to a client that asked for JSON.
  • /v1/models now publishes each model's structured-output support under airelays.structured_output (parameter, supported types): json_schema + json_object for Claude models, json_schema for OpenAI models (translated for the subscription backend, as before). The desktop Models tab and airelays models advertise it alongside the reasoning modes.

Fixed

  • Streaming requests no longer swallow errors (found by adversarial review). Request validation used to run inside the response generator, after the SSE headers were committed, so an invalid reasoning_effort or response_format on a stream: true request produced an empty 200 stream instead of an error. Claude streaming requests now validate before headers and return real 4xx status codes; both OpenAI streaming routes now contact the upstream and await its first event before committing headers, so upstream rejections surface as real status codes too. Failures that happen after streaming has started (provider timeouts, CLI failures) now emit an OpenAI-style in-band data: {"error": ...} event instead of silently truncating the stream — including the CLI's exit-0 error envelopes, which the streaming paths previously narrated as assistant content.
  • reasoning_effort is now honored on /v1/completions for OpenAI models (it was silently dropped there while chat completions and the Claude runtime honored it), and an explicit JSON null effort is treated as absent on both routes instead of being forwarded as reasoning: {"effort": null}.
  • Structured-output honesty hardening (found by adversarial review): a schema-enforced run that produces no schema-conforming output now fails loudly instead of serving prose or an upstream error message under the JSON contract; a response_format without a type is rejected like OpenAI does instead of silently degrading to unenforced text; oversized schemas (200 KB serialized cap; they travel on the local CLI's argv) and NaN/Infinity values get clean 422s instead of raw OS errors; and if a future CLI ever streamed more than one structured-output block, only the first is forwarded rather than concatenating into unparseable JSON.
  • The Claude runtime documentation no longer claims structured outputs are unsupported; docs/api.md, README, troubleshooting, and the LLM-readable indexes were aligned with the verified behavior above.