Repository navigation
Releases
v0.6.3
0.6.3 - 2026-09-18
Features
- Surface broken-config state in serve mode instead of crash-looping
Documentation
- Add awefork, awecontrib, and AgentX to the ecosystem section
- Point aweskill website links at aweskill.wehuman.top
- Retarget companion tool and self links to wehuman01
CI
- Adopt awecontrib verify entry point
v0.6.2
Three token-saver layers and multi-account logins: awecompress frozen-summary compression joins ODCP and RTK, subscription logins multiply via authHome, and a batch of integration hardening fixes lands with the first PyPI release of awecompress.
Added
awecompressprofile flag ("awecompress": true, or an object tuningsummaryModel("flash"default,"pro", or a literal model id that must be declared inproviders.jsonmodels),thresholdTokens,keepRecentTurns,minSpanTokens,transcriptResultCap,protectedTools,protectedFilePatterns). Ahead of ODCP/RTK, the oldest complete turns freeze into one cached summary — summary calls ride the flash (or chosen) destination with the provider's own auth, savings land in the usage log asawecompress_saved, and the frozen store is shared with the standalone proxy. Fail-open end to end;X-Awerouter-Token-Saver: offdisables it with the rest. Requirespip install awerouter[compress]— awecompress is now on PyPI as 0.2.0.- Provider
authHomefield (providers.json), legal only next to an"auth": "codex"/"auth": "claude"sentinel: codex reads<authHome>/auth.json(an aweswitch account dir works as-is —~/.config/aweswitch/accounts/codex/<name>); claude keeps its owned store at<authHome>/claude-auth.json. AbsentauthHomeis exactly the old single-login behavior. Combined with thepooltag (gateway) orbackups(profiles), same-model failover across accounts of one vendor becomes a few lines of config. - Per-login failover bookkeeping:
rejected_authskeys on sentinel+authHome, so a dead account's double-401 excludes only that account — its sibling accounts stay eligible later in the same request. Serve-start login warnings and 503/refresh hints name the exact dir that lacks a login, one line per login. awerouter config login claude <dir>/config logout claude <dir>(and codex's CODEX_HOME-aware hint, shell-quoted and Windows-safe): manage one account's login in its authHome; each claude store refreshes independently.odcpprofile flag ("odcp": true, or{"dedup": bool, "purgeErrors": bool | {"turns": int}}, default off). Before routing and before RTK, one pairing pass over the history applies two rules: dedup keeps only the newest output of repeated identical tool calls (same tool + normalized arguments — explicit JSONnullstays distinct from an omitted key), replacing older ones with a one-line placeholder; purgeErrors strips a failed call's input strings once it isturnsuser messages old (default 4), keeping the error text (anthropic only — other protocols carry no error mark on the wire). Editors and task/planning tools are never rewritten — including codex's shell-wrappedapply_patch— replacements that wouldn't shrink the body are skipped, and the trailing turn is untouched. Deterministic and fail-open, with theX-Awerouter-Token-Saver: offbypass;/v1/messages/count_tokensprunes identically. Unlike RTK, pruning rewrites earlier history when a new duplicate appears or an error crosses the age threshold — an accepted prompt-cache trade. Behavior follows Opencode-DCP's public documentation (AGPL-3.0); implemented from that description, not translated.odcp_savedis logged per request and reported byusagenext to rtk's; serve banner andserve statusshowodcpwhen on.
Fixed
- awecompress summary calls to Anthropic-shaped upstreams now carry
anthropic-version(the internal sender used to omit it, so Anthropic 400'd the summary call and compression silently failed open). - Gateway hot reload validates the fresh profiles' awecompress config (
summaryModeldeclarations, package presence) before swapping them in — an invalid reload is skipped and the previous config keeps serving, same as the single-profile reload. - Codex
config login/logouthints quote theCODEX_HOMEpath (spaces no longer break the printed command) and refuse paths containing a double quote on Windows (noset "injection into the copy-pasted command).
v0.6.1
Robust resident service management: serve restart one-word reloads, crash-proof request handling, and macOS installs that actually stick.
Fixed
- A request-path failure no longer kills the daemon: a
die()inside request handling (e.g. a${VAR}auth reference whose value is missing from the daemon's environment) used to raise SystemExit through the handler and take the whole process down — under KeepAlive, a crash loop. A guard middleware on both apps now answers that one request with 503 (message included) and keeps serving the next. - Resident installs on macOS no longer intermittently end up "installed — not running":
launchctl bootouttears down asynchronously, and right after it returns the old job can still read as loaded while a racing bootstrap is silently never submitted. install() now waits for the domain to actually release the old job, then bootstraps and verifies the job really loaded (retrying within 5s before dying loudly).serve restarthit this ~half the time before.
Added
awerouter serve restart [PROFILE]: the one-word way to apply a changed environment variable or secret. Resident services are re-installed from the current shell — same command line, port and host as installed, env baked fresh — and an installed-but-stopped service is started too. Plain background instances are stopped and re-spawned from the current shell on the same port; foreground instances are skipped (they belong to their terminal).
v0.6.0
Failover becomes first-class: routing destinations gain ordered backups queues with quota-aware cooldowns, providers declare a multimodal flag so the image guard survives failover, and gateway direct-forwards support a pool tag for same-model account rotation.
Added
- Failover queues (
backups) inrouting.json: each destination key may declare an ordered failover queue — same"provider,model"strings asdestinations. When the primary answers 429/408/5xx or a network error before the first streamed byte, the request walks its queue in order; smart routing itself never changes (the tier is the request's property — failover only swaps who serves it). Zero config, each tier carries one implicit cross-tier hop (flash→pro / pro→flash); an explicit list replaces it. A 429 or any response carryingRetry-Afterputs that candidate on an in-process cooldown (Retry-After honored when parseable, else 30s; capped at 60s). An exhausted queue passes the last upstream response through; a network-error 502 names every candidate tried. Every hop is stamped into the label (background→fb:glm,glm-4.7-flash) and counted asfallback_hopsin the usage log. - Provider
multimodalflag: image-bearing requests filter their failover queue on the provider's declaredmultimodalflag — the image guard holds during failover, and undeclared means no. - Provider
pooltag for gateway direct-forwards: entries sharing a tag within one protocol group are the same vendor's accounts (same models, different keys). A gatewayprovider/<model>forward that 429s fails over to the same model on the next pool member — declaration-order wrapping from the named entry, so each account's overflow flows to the next. One tag per entry replaces mutual-backups lists; untagged entries stay pinned. Cooldowns,→fb:labels,fallback_hops, and themultimodalimage guard all apply. - Track
rejected_authsto avoid reusing dead logins within a request.
Fixed
- Config load now validates that both
flashandprodestinations are present. - Skip implicit hop when both tiers point to identical provider+model.
- Scope cooldown keys by protocol so one protocol's 429 does not cool another's.
git_diffRTK filter defaults to a line cap;git_statushandles long-form Untracked files.- Update-check is resilient to fetch exceptions (preserves cached
latestVersionand records the check).
v0.5.9
Windows CI repair: since v0.5.5 the windows-latest CI legs have been red — v0.5.7's resident-service feature made serve status die on Windows, and the _is_awerouter_process tests assume a readable command line that Windows cannot provide.
Fixed
awerouter serve statusworks on Windows again:installed_services()now reports none on platforms without a service manager instead of exiting with the resident-services error (which is whatservice_kind()raised). Mutating paths (--install, resident stop/purge) still refuse loudly through the newrequire_service_kind()wrapper — same message as before (src/awerouter/service.py;TestStatusintests/test_cli.pyruns on Windows again)._read_cmdlineon Windows returns "unverifiable" (None) instead of a false"": there is no/proc, and MSYSpscannot see native processes, so the old fallback misclassified every live pid as dead. Theserve stopguard it feeds is POSIX-only today, but the contract no longer lies for any future Windows stop path (src/awerouter/runtime.py).TestIsAwerouterProcessskips on Windows with that reason — command-line matching cannot be observed where reading is impossible.serve statustests no longer read the developer's real LaunchAgents/systemd directories: a machine with a resident service installed failedTestStatuslocally even though CI was green (tests/test_cli.py).
v0.5.8
Two correctness fixes to v0.5.7's features: gateway direct forwards no longer pollute per-profile usage stats, and resident-service installs keep percent-encoded env values intact.
Fixed
- Gateway direct-forward usage attribution: a
<provider>/<model>forward belongs to no routing profile, but the usage log recorded it under whichever profile happened to serve the protocol, skewingusage --profile. Those requests now logdirectas profile/destination, with the real provider and model preserved (tests/test_gateway.py). - systemd
Environment=values escape%as%%(systemd expands % specifiers there), so a percent-encoded proxy URL baked into a resident service byserve run --installreaches the daemon verbatim (tests/test_service.py).
v0.5.6
Gateway mode: awerouter serve all turns all routing.json profiles into one local endpoint. A request model name selects the profile (<profile>/auto|flash|pro, e.g. step-glm/auto); GET /v1/models advertises every alias. The existing serve run <profile> model remains unchanged, so users can migrate one client at a time or run both forms concurrently.
Added
awerouter serve all [--port N] [--host HOST] [-d]: one daemon and one port for every profile. It preserves the same-protocol proxy contract and per-profile routing/settings/providers; requests select the target profile from the model prefix, thenautoruns the usual L1–L4 pipeline whileflash/proforce that profile's destination. The gateway ignores per-profileportfields (its own--portor the normal 20128 scan is authoritative), hot-reloads profile additions/removals and every config edit, registers asgatewayforserve status, andserve stop gatewaystops it. Background mode writesserve-gateway.log.- Top-level
routing.jsondefaultProfile: optional gateway-only bare-name target forauto/flash/pro, keeping existing Claude Code tier environment variables compatible. It must name a configured profile; with multiple profiles and no default, bare names fail loudly and callers must use<profile>/...; a lone profile is its own natural default. It is not valid inside a profile and does not affect single-profile serve. - Gateway discovery and honest failures:
/v1/modelslists default bare tiers when routable plus every profile alias;/identifies gateway mode. Unknown profile, invalid tier, and endpoint/profile protocol mismatch each return a descriptive 400 naming the problem and available alternatives./v1/messages/count_tokensfollows the same alias selection and model rewrite as inference. - Gateway-specific test coverage (alias selection, forced/custom tiers, default behavior, all protocols, errors, token counting, config parsing/reload and hints) plus README/README_cn documentation.
Fixed
- The gateway's registration name (
serve status/serve stop gateway/serve-gateway.log) now comes from a singleGATEWAY_PROFILE_NAMEconstant instead of scattered literals, so no real profile namedgatewaycan collide with it inconsistently.
v0.5.5
Daemon lifecycle becomes first-class: awerouter serve run <profile> -d runs the daemon detached — it survives the terminal (log: <state>/serve-<profile>.log), awerouter serve status lists every running serve instance (foreground and background, registered under <state>/run/ at bind time), and awerouter serve stop [PROFILE] stops them with a graceful SIGTERM. Serve now also watches routing.json/providers.json and hot-reloads edits — destinations, thresholds, tool routing, settings overrides, and provider entries apply to the next request without a restart. The CLI also reorganizes: serve becomes a command group (run / status / stop) and login / logout / restore move under config — the released top-level spellings keep working as hidden aliases, so nothing breaks.
Added
login,logout, andrestoremoved under theconfiggroup —awerouter config login [claude|codex],awerouter config logout [claude|codex],awerouter config restore [providers|routing]— grouping every command that manages config-owned state (files, backups, the claude login store) next toconfig path/show/edit. The released top-level spellings keep working as hidden aliases (awerouter loginetc.), so existing docs, scripts, and muscle memory don't break;awerouter --helplists only theconfigform. Error hints that name the command (missing claude login 503/warnings,init --mergebehavior-shift note) now print the new spelling.serveis now a command group —run(start a daemon),status,stop— instead of a bare command with top-levelstatus/stop. The pre-group spellings keep working:awerouter serve <profile> [--port/--host/-d]andawerouter <profile>are equivalent toawerouter serve run <profile>(an unknown subcommand underserveis a profile name, same as the top level), and bareawerouter servestill auto-selects the single profile. The one removal:awerouter serve -d(background auto-select, no profile) — name the profile or useserve run.awerouter serve run [PROFILE] -d/--background: spawns the daemon detached viapython -m awerouter __serve_daemon__ <profile>(new__main__.py), waits for it to bind, then prints pid/port/log path plus astatus/stophint; notes when the profile is already running (starts another anyway, same as concurrent foreground instances). A child that dies before binding fails the command with the log tail. POSIX-only (os.name == "nt"refuses).awerouter serve status/awerouter serve stop [PROFILE](newawerouter.runtimemodule): every serve process registers{pid, profile, protocol, port, host, background, started}under<state>/run/<pid>.jsonat bind time and unregisters on shutdown (SIGTERM/SIGHUP now shut down gracefully via signal handlers on the serve event loop).serve statuslists live instances (stale/dead-pid files pruned on sight);serve stopSIGTERMs matching instances after a command-line guard (pstoken match:awerouter,-m awerouter,.../bin/awerouter) so a reused pid is never signaled. Windows:pid_aliveprobes viaOpenProcess(os.kill(pid, 0) would TerminateProcess there), and-d/serve stoprefuse outright.- Hot reload:
_servepolls both config files' mtimes (1s) and swaps the live app'sprofile/settings/providersfrom a freshload_for_profile— destinations, thresholds, tool routing, per-profile overrides, provider entries, and"auto"threshold re-resolution all apply to the next request without a restart. A file that fails to load prints oneconfig reload skippedline and the previous config keeps serving until the file parses again; a changedportfield prints a restart hint (rebinding is out of scope). The serve banner printshot reload -> on. - Skill refresh nudge:
awerouter self-update(after a successful upgrade) and the daily update reminder now add one line when the awerouter agent skill is detected in~/.agents/skills/or~/.claude/skills/— the skill is updated along with awerouter releases, and refreshing it is best done by the coding agent itself:aweskill update awerouter. awerouter never writes the skill file: its lifecycle (store, projections, local-edit protection) belongs to aweskill, and the skill is not packaged in the wheel, so awerouter only points at the refresh command.
v0.5.4
Image bridge: settings.imageBridge (default false, opt-in) gives a text-only pro model second-hand vision. When a request carries images only in history — the final message is text — awerouter has the multimodal flash destination (imageModel) transcribe each distinct image, replaces the image blocks with the transcription text before routing, and the session falls through to defaultModel (pro) instead of being pinned to flash forever. A fresh upload this turn still routes to flash natively; router.py is unchanged — after the rewrite has_image is false and the request lands in L3/default, before the rewrite the L1 image guard routes it as always. The step-glm-mm template now ships with imageBridge: true.
Added
settings.imageBridge(true/false, global or per profile, validated at load and shown byconfig show): the request-pipeline transform described above. It runs before rtk compression and routing in_proxy_flow, so what rtk compresses, what L3 scores, and what goes upstream are exactly the same body;handle_count_tokensapplies the same bridge so the client's context-window estimate matches. The serve banner printsimage bridge -> on (...)naming the transcribing destination.awerouter.vision(pure logic): per-protocol content keys (image_key), caption body building and response parsing for all three wire protocols, in-place image-block rewriting ([Image n, transcribed by <model>]text blocks), and a content-addressed caption cache (bounded FIFO, process lifetime — a restart re-transcribes each distinct image once; the cache key includes provider+model, so a destination switch never serves another model's transcription).- Failure fallback: any caption call failure (network, non-200, empty text) leaves the request body untouched — one printed line, and the L1 image guard routes the request exactly as before. Codex subscription logins (
auth: "codex", SSE-only backend) skip the bridge entirely. step-glm-mmtemplate setsimageBridge: true;init --mergeflags a newly-writtenimageBridgeas a behavior shift next toimageModel/defaultModel. README/README_cn document it as an "Image bridge" (图片桥接) section; README.ai.md and the awerouter skill mention the template flag.
Behavior notes
- Bridged "vision" is second-hand: its ceiling is the transcription quality, and each distinct image costs one extra flash call (cached per image content for the process lifetime).
- The flash→pro rescue fallback (flash down on an un-rewritten image request) still carries raw images to a possibly text-only pro — pre-existing behavior, unchanged and out of scope.
v0.5.3
Per-profile settings overrides and template merging: every settings key (backgroundModel, thinkModel, webSearchModel, imageModel, defaultModel, searchResultDiscount, toolRouting, longContextAuto) can now be written directly in a profile body, flat next to protocol/destinations — it applies to that profile alone, and missing keys (missing fields inside toolRouting/longContextAuto included) inherit from the global settings block, so a profile re-tunes exactly what differs. backgroundModel/thinkModel in a profile body are the pre-v0.5.2 per-profile shape, honored again. Serve and the router use the merged result (RoutingProfile.settings); a profile's own longContextAuto.fallbackThreshold now backs its "auto" threshold; usage calibrate/token views with --profile use that profile's discount. config show <profile> prints the effective values with the override keys flat in the entry, the serve banner lists them on an overrides -> ... line. Unknown keys die at load naming the offender — profile bodies now validate their key set too (previously silently ignored), and the global block gets the same check, so a typo'd key never silently inherits.
awerouter init <template> --merge folds a bundled template into an existing config: missing provider entries, profiles, and settings keys are filled in; existing entries always win (your base_url/auth, your profile bodies, your explicitly-set settings). Profile id collisions are skipped and reported, so merging is idempotent. Newly-written imageModel/defaultModel — global keys that re-route every existing profile — print a warning with an awerouter restore hint. Both files are snapshotted to .bak before writing and the merged config is load-validated before init reports done.
Added
- Per-profile settings overrides: every
settingskey may also be written directly in a profile body, flat next toprotocol/destinations— it applies to that profile alone, and missing keys (missing fields insidetoolRouting/longContextAutoincluded) inherit from the globalsettingsblock, so a profile re-tunes exactly what differs (e.g. one multimodal sidekick flippingimageModel/defaultModelwithout touching everyone else).backgroundModel/thinkModelin a profile body are the pre-v0.5.2 per-profile shape, honored again. Serve and the router use the merged result (RoutingProfile.settings); a profile's ownlongContextAuto.fallbackThresholdnow backs its"auto"threshold;usage calibrate/token views with--profileuse that profile's discount.config show <profile>prints the effective values with the override keys flat in the entry, the serve banner lists them on anoverrides -> ...line. Unknown keys die at load naming the offender — profile bodies now validate their key set too (previously silently ignored), and the global block gets the same check, so a typo'd key never silently inherits. awerouter init <template> --mergefolds a bundled template into an existing config: missing provider entries, profiles, and settings keys are filled in; existing entries always win (yourbase_url/auth, your profile bodies, your explicitly-set settings). Profile id collisions are skipped and reported, so merging is idempotent. Newly-writtenimageModel/defaultModel— global keys that re-route every existing profile — print a warning with anawerouter restorehint. Both files are snapshotted to.bakbefore writing and the merged config is load-validated beforeinitreports done.