Repository navigation
Conversation
The chat UI replaces an AUTH failure with "API key is invalid" and a QUOTA one with "Request quota exhausted.", so a plane turn the gateway refused for its own reasons (free tier, usage cap, region, retired model, unsupported protocol) read as a bad key or an empty quota. A new pure module, src/utils/gateway-refusal.ts, reads the gateway's typed refusal from pi's failure text and finishes the turn as FREE_TIER or MODEL_UNAVAILABLE instead: neither code is replaced by the UI and neither is retried by default, so the user sees the gateway's own words and, except on Google-protocol models, its status. The plane adapter applies it in prepareCall and stream; pi-ai's own routes and stepped-aside planes keep the bridge's codes. The free-tier tool fallback now treats a read or bash tool without properties as missing, replacing it rather than adding a duplicate, and sends tool_choice "none" on a free chat-completions body that carried no tools. The keyed probes gain recorded rows for a raw session id, session-only headers and the harness User-Agent, plus a free baseline; a keyless usage-cap 429 now reads as free-usage-limited and is left out of the Retry-After record. The README gains the remaining bullets of its "With OpenCode Console installed" section. The rate-limit and reasoning-effort lessons land no code: the recorded run answered no keyed 429 and never measured the field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase P-R3 of docs/improvement-plan.md, stacked on #173.
What changed
src/utils/gateway-refusal.ts. It reads the gateway's typed refusal from pi's failure text and maps it to one of two codes.FREE_TIERcoversFreeTierErrorandFreeUsageLimitError.MODEL_UNAVAILABLEcoversRegionError,ModelDeprecatedandModelProtocolUnsupported. Neither code isAUTH,QUOTAorACCOUNT_QUOTA, so the chat UI shows the gateway's own words instead of replacing them with "API key is invalid" or "Request quota exhausted.". Neither code is in the host's default retryable set. pi's message is kept unchanged, andstatusis read only from the head of the text.PlaneAdapter.prepareCallandstream()wrap the prepared stream and rewrite the finalfinishfailure. pi-ai's own routes and stepped-aside planes (D27) keep the bridge's codes.src/tool-fallback.tsnow treats areadorbashtool without non-emptypropertiesas missing, and replaces it rather than adding a duplicate. A free chat-completions body that had no tools getstool_choice: "none". Responses bodies are never touched.test/support/keyed-probes.tsadds four kinds of row: OC9 (a raw, non-ses_session id, keyed and free), OC8 (session-only headers, keyed), OC6 (the harness User-Agent, keyed and free), and a free baseline. A keyless 429 from the usage cap now reads asfree-usage-limited, notrate-limited, and is left out of theRetry-Afterrecord. They are formatted into the variants table indocs/redirect-planes.md§13.Retry-Afterto act on. L10'sreasoning_effort: "none"was never measured: the reasoning model was refused both with and without the field.AGENTS.md,CONTRIBUTING.md,docs/testing.mdand the CI comments now describe the above. New tests:test/gateway-refusal.test.ts,test/mock/plane-refusals.mock.ts, and new cases in the plane-provider, tool-fallback, keyed-probes and endpoint-record tests.Behaviour changes
AUTH,QUOTA,INVALID_REQUESTorPI_AI_ERROR, depending on how the bridge read the text. It now finishes asFREE_TIERorMODEL_UNAVAILABLE. The conversation shows the gateway's words and, except on Google-protocol models (whose failure text carries none), its status. It is not retried by default.readorbashthat has no properties instead of adding a second copy. It sendstool_choice: "none"on a free chat-completions turn that had no tools.Retry-Afterline now reads "Retry-Afteron a keyed 429: no keyed 429 was answered." (it was "no 429 was answered."). This keeps it from appearing to contradict keyless free rows that answerfree-usage-limited (429)in the variants table.Review
Three rounds, each with correctness, tests and rules lenses, and a verify pass after each fix round.
Round 1 found drift in the evidence and docs: L10 read as "not accepted", a cited run that §13 did not hold, the chat UI replacing five codes rather than two, and the Google no-status qualifier. It also found assertions that could never fail. Round 2 caught the keyless usage-cap 429 being recorded as
rate-limited, and an L7 guide naming a verdict the table could never print. Round 3 caught doc, comment and test-name drift left by round 2's narrowing ofRetry-Afterto keyed rate-limit 429s. None of round 3's fixes changed behaviour beyond the record line above.These reviews ran on the general model instead of the dedicated reviewer, because the dedicated reviewer was unavailable: spec:P-R3, review:P-R3:correctness#1, review:P-R3:rules#1, verify:P-R3#1.1, review:P-R3:correctness#2, verify:P-R3#2.1, review:P-R3:correctness#3, verify:P-R3#3.1.
Verification
pnpm run checkvp test run(alpha channel)vp test runshuffled, seeds 1 and 42vitest.mock.config.ts)pnpm run build+scripts/check.tslib/index.mjs358.69 kB); consumer install with plain npm (19 entries);lib/client.js136211 bytes, under the 139264 tripwire; check passedvp test run --coveragesk-probe-…test fixtureThe check, unit (alpha), mock and build/
check.tsrows were re-run on the final tree immediately before this commit, with the same counts. The other rows come from the phase's gate run after the last fix round. The freshness scope answersreportfor all three checks on this diff, socatalog freshnessdoes not gate this PR.🤖 Generated with Claude Code
https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD