Upgrade the Pi SDK and refresh its model catalog - #867
Conversation
|
Live testing exposed one bundle-only OAuth failure: Pi’s lazy OAuth loader resolved Proof: the regression test failed before the change and passes after it; all 803 agent-runtime tests pass; the tarball smoke passes; and a bundled |
5e157ef to
9b352a3
Compare
|
Rebased onto
Two things that may make the Node floor less of a decision than it looks:
Happy to split this differently if you'd rather review the namespace migration and the runtime unification separately — say the word and I'll restructure. |
listPiBridgeModels called modelRuntime.refresh({ allowNetwork: true }) on
every invocation, so each model list fanned out one network request per
configured provider to pi.dev. It runs on every picker render and on
thread-detail bootstrap, and nothing caches the result between the browser
and the bridge. When a provider endpoint stalls the call burns the full 5s
abort budget before returning.
Measured cost of one listPiBridgeModels call: ~8-9ms warm, 274-375ms cold,
and 5,010ms on 2 of 6 cold runs.
Attempt the network refresh at most once per bridge process. The bridge is
long-lived, so later calls resolve from the persisted store plus the bundled
static catalog and return the same model set.
Also stop memoizing a rejected ModelRuntime.create(): a single transient
failure was otherwise cached for the life of the process, leaving every
model list and session start broken until the bridge restarted.
|
CI caught a real defect in the previous push — fixed and force-pushed. The Node 20 cleanup had removed the Verified locally on the pushed commit, replicating the CI shard:
|
9b352a3 to
9ce7023
Compare
|
Resolved the The owning invariant is now consistent end to end: Pi's model catalog, server provider policy, and daemon bridge all accept Proof on
The full local host-daemon suite still has the previously documented environment-only failure because this machine's Codex 0.101.0 is below bb's 0.136.0 minimum; the protocol-focused host-daemon test passes and GitHub CI is running on the pushed commit. |
|
Hey @amadad I think how I'm going to solve this is:
Does that make sense to you? |
|
Yeah @SawyerHood. Unbundling deletes the class of problems this PR works around (static Auth loaders, jiti/static, per-call catalog refresh all exist because F ships inside bb). FWIW, bb already works this way with Codex, and I hit Codex 0.101.0 is too old for this bb version twice on this branch, reproducing on a clean origin/main It surfaces late and reads like a bb bug rather than "update Codex" - worth getting the version handshake right for Pi from the start. Happy to close this. The max reasoning fix (PI_SERVER_CAPABILITIES + protocol 66) is server-side and survives unbundling - it closes #875 - so I can send it standalone if useful. |
|
I think I will end up going with your approach for now since it more or less keeps the status quo. I just dug into the pi acp adapter and it has some issues. I'll take a pass to do some deeper review and testing today |
Follow-up fixes to the Pi SDK upgrade, all in areas the upgrade itself introduced or changed. - Stop rejecting untested Node release lines. `install-machine.sh` moved from an open-ended floor to a closed allow-list of 22/24/26, which hard -fails installs on Node 25 today and on every future line the day it ships. Pi only requires >=22.19, so gate on that floor and keep the tested-lines list as documentation. - Handle the model catalog network refresh instead of discarding it. `ModelsRefreshResult` carries per-provider errors and an aborted flag; both were dropped silently, so a persistently failing refresh was invisible and a single cold-start timeout pinned the bridge to the stale catalog for the life of the process. Log the failures and allow a bounded number of retries. - Hold the refresh as a promise rather than a boolean. The latch was set before the await, so concurrent `model/list` calls raced past it and could read a half-refreshed snapshot. - Honor `PI_OFFLINE`. `ModelRuntime` disables catalog network access when it is set; passing `allowNetwork: true` unconditionally overrode that. - Guard the bundled OAuth registration. `registerBunOAuthFlows()` reaches Pi's static loaders because the bridge ships as one file; a rename upstream would otherwise surface only at login time inside the packaged bridge. `check-bundles` now asserts the flows landed in the bundle. - Build the server inference registry lazily. `builtinModels()` at module scope turned any failure inside it into a server import failure. - Reconcile a stored reasoning override whenever the effective model no longer advertises it, not only on a model switch. Pi reports provider-verified thinking levels, so a model can lose a level between catalog refreshes. - Document the `@opentelemetry/api` pin, which is invisible from package.json alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verified against the real bundled bridge: with pi.dev unreachable, the first two model/list calls each paid the full 5s timeout because every caller awaited the in-flight retry. Only the first attempt blocks now. Once it settles, later calls start a background retry and answer immediately from the current catalog, so a host with no route to pi.dev pays one timeout instead of one per early picker render. Measured on the bundle: second call went from 5004ms to 2ms, with the retry still running and still logged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pi 0.82 added AgentSession._emitAgentSettled, which emits `agent_settled` on the session event stream after every agent run. Pi 0.70.5 had no such event. BB's adapter does not translate it, so it fell through to the `provider/unhandled` fallback and rendered as a literal "Unhandled Pi event" row in the user's transcript on every single turn. Found by driving a real thread through the dev app with the browser, not by reading the diff: the row is visible in the UI and the event is persisted to the thread event log. Add an explicit ignore list rather than translating it. BB already derives turn completion from `agent_end` plus its `willRetry` flag, so the settle signal carries nothing extra. The list gives the next Pi bump a named place to classify new events instead of leaking them to the UI. Verified: a fresh pi thread after the fix records 13 events and zero `provider/unhandled`, versus one per turn before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Reviewed this and pushed three follow-up commits to the branch rather than leaving a long list of review notes. Summary of what changed and why, since a couple of these were only findable by running the thing:
I also updated the PR body — the compatibility section described the old closed Node range, and since this repo squash-merges, that text becomes the permanent commit message. Verified end to end against real |
Summary
@mariozechner/pi-*0.70.5 to@earendil-works/pi-*0.82.0.ModelRuntimefor model resolution, authentication, session creation, and catalog refresh. The picker now reflects each model's supported reasoning levels, includingmax.agent_end.willRetry.jiti/staticdependency and register Pi's static OAuth loaders so the standalone bridge can authenticate without filesystem-only dynamic imports.Why
BB was pinned to Pi 0.70.5 under the retired package namespace. Its static model APIs could not expose models added through Pi's current catalog. The new runtime API keeps session and catalog behavior under one owner while preserving BB's existing bridge protocol.
The server pins
@opentelemetry/apito the workspace version so Pi AI and Drizzle resolve one shared type identity. This pin is a build-time type-identity workaround expressed as a runtime dependency; see the "Pinned Dependencies" table indocs/repository-overview.mdbefore changing it.Compatibility
engines.nodefloor. 22.19, 24, and 26 are the tested lines.install-machine.shgates on the floor only rather than an allow-list of tested majors, so a release line we have not tested yet still installs instead of hard-failing the day it ships. Thebb-appnpmenginesfield lists the tested lines, which npm surfaces as a warning rather than an install failure.maxas a Pi reasoning level, soHOST_DAEMON_PROTOCOL_VERSIONmoves 65 → 66.Review follow-ups included in this PR
ModelsRefreshResultcarries per-provider errors and anabortedflag; both were discarded, so a persistently failing refresh was invisible and one cold-start timeout pinned the bridge to a stale catalog for the life of the process. Now logged, with bounded retries.await, so concurrentmodel/listcalls could read a half-refreshed snapshot. Replaced with a memoized promise. Only the first attempt blocks a caller; retries run in the background, so a host with no route to pi.dev pays one timeout rather than one per early picker render.PI_OFFLINE.ModelRuntimederives its network policy from this variable, and passingallowNetwork: trueunconditionally overrode it. Now honored.registerBunOAuthFlows()reaches Pi's static loaders because the bridge ships as a single file; an upstream rename would otherwise surface only at login time inside the packaged bridge.check-bundlesasserts the flows landed in the bundle.agent_settled. Pi 0.82 addedAgentSession._emitAgentSettled, an event that does not exist in 0.70.5. BB's adapter did not translate it, so it fell through toprovider/unhandledand rendered a literal "Unhandled Pi event" row in the transcript on every turn. Added an explicit ignore list.builtinModels()at module scope turned any failure inside it into a server import failure rather than a failure of the one inference call.Verification
pnpm install --frozen-lockfilepnpm exec turbo run build typecheck lint --concurrency=1 --force(68 tasks)bb-app: 2 files, 47 testspnpm exec turbo run smoke:tarball --filter=bb-app --forcecheck-bundles, including the new OAuth-literal assertion. Verified non-vacuous: removing the registration and rebuilding fails the check.End-to-end against real
openai-codexOAuth credentialsopenai-codex/gpt-5.6-sol— plain reply, real tool use (read, correct file contents), and multi-turn session memory. Same atmaxreasoning.auth.jsonyields 0 models; with credentials, 7 models and only the authenticated provider. Confirms removing the explicithasAuthfilter did not widen the picker.maxreasoning replies correctly; the composer renders "GPT-5.6 Sol Max"; the GPT-5.6 family advertises[low, medium, high, xhigh, max]while 5.3/5.4/5.5 stop atxhigh.model/liststill returns a usable catalog, the failure is logged, and the retry no longer blocks later callers (second call 5004ms → 2ms).agent_settled: a fresh Pi thread after the fix records zeroprovider/unhandledevents, versus one per turn before.