feat(providers/pi): resolve models via Pi's ModelRegistry to support custom providers#1417
Conversation
…custom providers Previously the Pi adapter looked up models via pi-ai's static catalog (getModel), so user-defined providers in ~/.pi/agent/models.json — the same file the `pi` CLI writes when you add a custom OpenAI-compatible or Anthropic-compatible proxy — failed with 'Pi model not found' even though `pi` CLI accepted them. This made it impossible to route Archon workflows through an internal LLM gateway, a company proxy, or any OpenAI-compatible endpoint that isn't in the built-in catalog. Now the adapter constructs ModelRegistry.create(authStorage) instead of inMemory(), which loads ~/.pi/agent/models.json on its own. Model lookup goes through registry.find(provider, modelId), covering both the built-in catalog AND user-defined custom providers. Auth fast-fail uses registry.hasConfiguredAuth(model), which picks up apiKey fields inside models.json's provider block so the adapter doesn't reject a model that carries its own credentials. Error messages distinguish the two code paths: missing models point at both the built-in catalog URL and the models.json self-service route; missing credentials hint at env vars / OAuth for built-in providers and at the models.json apiKey field for custom providers. Malformed models.json is surfaced as a system warning rather than aborting workflows that only use built-in providers. Removed the static import of pi-ai — model resolution no longer needs it. Added type-only import of ModelRegistry so the lazy-load contract is preserved (see the file's header comment on why value imports from @mariozechner/* are forbidden at module scope). Tests: replaced mockGetModel / mockModelRegistryInMemory with a single configurable mockModelRegistryCreate that exposes find / hasConfiguredAuth / getError. All 58 existing PiProvider assertions retained; added two new regression tests — one pinning the custom- provider end-to-end flow (baseUrl threaded to createAgentSession) and one verifying malformed-models.json becomes a system chunk without aborting the run.
📝 WalkthroughWalkthroughThe Pi provider's model-resolution mechanism migrates from Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/providers/src/community/pi/provider.ts`:
- Around line 289-296: The user-facing messages currently hardcode
"~/.pi/agent/models.json" even though ModelRegistry.create(authStorage) respects
PI_CODING_AGENT_DIR; update the warning texts (the block that checks
modelRegistry.getError() and the similar messages later) to compute the actual
path used by ModelRegistry by resolving process.env.PI_CODING_AGENT_DIR or
falling back to path.join(os.homedir(), '.pi', 'agent', 'models.json'), and
include that resolved path in the process/yielded error content so users see the
real file to fix; locate references around piCodingAgent.ModelRegistry.create
and modelRegistry.getError to apply the change consistently for all three
messages.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8d179e5a-58cf-490a-b646-5dac6bd222cf
📒 Files selected for processing (2)
packages/providers/src/community/pi/provider.test.tspackages/providers/src/community/pi/provider.ts
| const modelRegistry = piCodingAgent.ModelRegistry.create(authStorage); | ||
| const modelsJsonError = modelRegistry.getError(); | ||
| if (modelsJsonError) { | ||
| yield { | ||
| type: 'system', | ||
| content: `⚠️ Pi could not load custom providers from ~/.pi/agent/models.json:\n${modelsJsonError}\nBuilt-in providers still work; fix the file to use custom providers.`, | ||
| }; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
@mariozechner/pi-coding-agent ModelRegistry.create default models.json path PI_CODING_AGENT_DIR
💡 Result:
The default path for models.json in ModelRegistry.create(authStorage) is join(getAgentDir, "models.json"), where getAgentDir returns the directory specified by the PI_CODING_AGENT_DIR environment variable (default: ~/.pi/agent). Thus, the default full path is $PI_CODING_AGENT_DIR/models.json or ~/.pi/agent/models.json.
Citations:
- 1: https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/model-registry.ts
- 2: https://www.npmjs.com/package/@mariozechner/pi-coding-agent?activeTab=dependents
- 3: https://agent-safehouse.dev/docs/agent-investigations/pi
- 4: https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/README.md
🏁 Script executed:
# First, let's examine the file at the specified lines
head -320 packages/providers/src/community/pi/provider.ts | tail -50Repository: coleam00/Archon
Length of output: 3126
🏁 Script executed:
# Also search for all occurrences of the hardcoded path and PI_CODING_AGENT_DIR in the file
rg -n "\.pi/agent" packages/providers/src/community/pi/provider.tsRepository: coleam00/Archon
Length of output: 1566
🏁 Script executed:
# Check for PI_CODING_AGENT_DIR usage in the file
rg -n "PI_CODING_AGENT_DIR" packages/providers/src/community/pi/provider.tsRepository: coleam00/Archon
Length of output: 222
🏁 Script executed:
# Check imports in the file to see what's available from piCodingAgent
head -50 packages/providers/src/community/pi/provider.ts | grep -E "import|from"Repository: coleam00/Archon
Length of output: 1312
🏁 Script executed:
# Search for how PI_CODING_AGENT_DIR is used elsewhere in the codebase
rg -n "PI_CODING_AGENT_DIR" packages/ --type tsRepository: coleam00/Archon
Length of output: 444
🏁 Script executed:
# Check if os.homedir() or path expansion is used elsewhere for similar cases
rg -n "homedir|expandUser|~/" packages/providers/src/community/pi/ --type ts -A 2Repository: coleam00/Archon
Length of output: 11298
🏁 Script executed:
# Check if piCodingAgent exports a getAgentDir function
rg "getAgentDir|agentDir" packages/providers/src/community/pi/ --type tsRepository: coleam00/Archon
Length of output: 174
🏁 Script executed:
# Check the pi-coding-agent package to see what's exported
npm list `@mariozechner/pi-coding-agent` 2>/dev/null || echo "Checking package.json for version"Repository: coleam00/Archon
Length of output: 138
🏁 Script executed:
# Look for how the codebase resolves home paths for Pi directories
grep -r "homedir\|HOME" packages/providers/src/community/pi/ --include="*.ts" -B 2 -A 2Repository: coleam00/Archon
Length of output: 2879
🏁 Script executed:
# Check package.json for pi-coding-agent dependency
grep -A 2 "pi-coding-agent" packages/providers/package.jsonRepository: coleam00/Archon
Length of output: 180
🏁 Script executed:
# Look for any getAgentDir or similar exports used in the codebase
rg "piCodingAgent\." packages/providers/src/community/pi/provider.ts | head -20Repository: coleam00/Archon
Length of output: 417
🏁 Script executed:
# Check if there's a way to get the resolved path from the ModelRegistry
rg "getAgentDir" packages/ --type ts -B 2 -A 2Repository: coleam00/Archon
Length of output: 645
🏁 Script executed:
# Check if there's documentation or examples of how to get the agent dir
npm view `@mariozechner/pi-coding-agent`@0.67.5 | grep -A 20 "getAgentDir\|agent.*dir" || echo "Not found in npm"Repository: coleam00/Archon
Length of output: 76
🏁 Script executed:
# Search for any usage of PI_CODING_AGENT_DIR with actual path construction
rg "process\.env\.PI_CODING_AGENT_DIR" packages/ -B 3 -A 3 --type tsRepository: coleam00/Archon
Length of output: 41
🏁 Script executed:
# Check how the Pi library documentation handles this in examples/tests
rg "ModelRegistry|getAgentDir" packages/providers/src/community/pi/provider.test.ts -B 2 -A 2Repository: coleam00/Archon
Length of output: 2775
🏁 Script executed:
# Check if there's a helper or utility function that could resolve this
rg "homedir|join.*\.pi.*agent" packages/providers/src/community/pi/ --type ts -B 2 -A 2Repository: coleam00/Archon
Length of output: 1999
🏁 Script executed:
# Look at the full context around the paths to see if we should use a helper
sed -n '275,330p' packages/providers/src/community/pi/provider.tsRepository: coleam00/Archon
Length of output: 3451
🏁 Script executed:
# Check what's imported at the top of provider.ts to see if we need to add imports
head -30 packages/providers/src/community/pi/provider.tsRepository: coleam00/Archon
Length of output: 1434
Fix hardcoded path to reflect PI_CODING_AGENT_DIR env var in all user-facing messages.
The warning at line 294 and similar messages at lines 304 and 316 hardcode ~/.pi/agent/models.json, but the code comments explicitly document that ModelRegistry.create() respects the PI_CODING_AGENT_DIR environment variable. Users setting this env var see incorrect paths in error messages and may edit the wrong file.
The suggested fix needs correction: tilde (~) is not expanded in Node.js. Use homedir() from node:os instead:
Corrected fix
+import { homedir } from 'node:os';
+
+ const piAgentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), '.pi', 'agent');
+ const modelsJsonPath = join(piAgentDir, 'models.json');
const modelRegistry = piCodingAgent.ModelRegistry.create(authStorage);
const modelsJsonError = modelRegistry.getError();
if (modelsJsonError) {
yield {
type: 'system',
- content: `⚠️ Pi could not load custom providers from ~/.pi/agent/models.json:\n${modelsJsonError}\nBuilt-in providers still work; fix the file to use custom providers.`,
+ content: `⚠️ Pi could not load custom providers from ${modelsJsonPath}:\n${modelsJsonError}\nBuilt-in providers still work; fix the file to use custom providers.`,
};
}Apply the same path resolution to lines 304 and 316.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/providers/src/community/pi/provider.ts` around lines 289 - 296, The
user-facing messages currently hardcode "~/.pi/agent/models.json" even though
ModelRegistry.create(authStorage) respects PI_CODING_AGENT_DIR; update the
warning texts (the block that checks modelRegistry.getError() and the similar
messages later) to compute the actual path used by ModelRegistry by resolving
process.env.PI_CODING_AGENT_DIR or falling back to path.join(os.homedir(),
'.pi', 'agent', 'models.json'), and include that resolved path in the
process/yielded error content so users see the real file to fix; locate
references around piCodingAgent.ModelRegistry.create and modelRegistry.getError
to apply the change consistently for all three messages.
|
Duplicate of #1284 |
|
Closing as resolved by #1284 |
Summary
pi-ai's static catalog (getModel()). User-defined providers in~/.pi/agent/models.json— the same file thepiCLI writes when you/models adda custom OpenAI-compatible or Anthropic-compatible proxy — fail withPi model not found: provider='<x>' model='<y>'even though thepiCLI itself accepts them.piCLI expect parity when the same account runs workflows through Archon.pi-ai.getModel()(static catalog only) topiCodingAgent.ModelRegistry.create(authStorage).find(provider, modelId)(static catalog +~/.pi/agent/models.json). Auth fast-fail moves fromauthStorage.getApiKey(provider)tomodelRegistry.hasConfiguredAuth(model)so custom providers whoseapiKeylives inside themodels.jsonprovider block aren't falsely rejected. Malformedmodels.jsonsurfaces as a non-fatal system warning.PI_CAPABILITIES. No change to session resume, env-var precedence, extension loading, skills/tools resolution, structured output, or the Pi lazy-load contract (type-only import ofModelRegistryadded; pi-ai static import removed because it's unused now). No change to Claude or Codex providers. No new env vars, no new config fields, no DB migration.UX Journey
Before
After
Architecture Diagram
Before
After
Connection inventory:
pi/provider.tspi-ai.getModelApi,Model) remainpi/provider.ts@mariozechner/pi-coding-agentModelRegistry(type)PiModelRegistry; preserves lazy-loadpi/provider.tspiCodingAgent.ModelRegistry.inMemory.create()pi/provider.tspiCodingAgent.ModelRegistry.create~/.pi/agent/models.jsonvia Pi'sgetAgentDir()pi/provider.tsregistry.findpi-ai.getModel()for model resolutionpi/provider.tsregistry.hasConfiguredAuthauthStorage.getApiKey()for auth fast-failpi/provider.tsregistry.getErrormodels.jsonparse errors as a system chunkLabel Snapshot
risk: lowsize: Mcore(community provider code)providers:piChange Metadata
featurecoreLinked Issue
(No existing issue — raising this directly since the fix is small and self-contained. Happy to open an issue first if maintainers prefer that order.)
Validation Evidence (required)
@archon/providerspi test file: 58 pass / 0 fail (55 pre-existing + 2 new regressions around custom providers + 1 around malformedmodels.json).inMemoryregression would fail loudly.Security Impact (required)
ModelRegistry.create()reads~/.pi/agent/models.json, which thepiCLI already reads and writes; Archon's Pi adapter was already reading~/.pi/agent/auth.jsonand~/.pi/agent/sessions/**from the same directory.baseUrlis user-controlled via their ownmodels.json; a user adding a malicious provider there has already compromised their pi credentials.models.json's provider block (theapiKeyfield) are now consulted for auth fast-fail (hasConfiguredAuth) and at HTTP-request time (Pi SDK'sgetApiKeyAndHeaders). This is the same code path thepiCLI uses, and the same file the user already trusts pi to read. Archon does not log, copy, or re-emit the key.~/.pi/agent/), same file (models.json) that Pi itself owns.Compatibility / Migration
anthropic/…,openrouter/…, etc.) resolve identically throughregistry.find(). Error messages changed shape but still include the built-in catalog URL..archon/config.yamlschema unchanged. A user who does not have~/.pi/agent/models.jsonsees identical behavior (the file is optional;ModelRegistry.create()tolerates its absence).Human Verification (required)
Verified scenarios (on macOS arm64, Bun 1.3.x, dev mode):
pi --printwithanthropic/claude-*(both 200 and 403 responses) surfaces through unchanged — the same auth resolution path runs..archon/config.yamlwithmodel: <custom-provider>/<model>, where<custom-provider>is defined in my local~/.pi/agent/models.jsonwithbaseUrl: https://<redacted-proxy>and anapiKey.bun run cli workflow run archon-assist "hi"completed successfully; logs showprovider.pi session_started {piProvider: "<custom>", modelId: "<m>", …}and the response is the real model's self-identification.<valid-provider>/<bogus-model>surfaces the new error text containing both the built-in catalog URL and themodels.jsonself-service hint.models.json(intentionally broken file): workflow still runs for built-in refs; the system-chunk warning appears in the transcript. Fixing the file and re-running clears the warning.ANTHROPIC_API_KEYetc.) still firesetRuntimeApiKeyfor built-in providers and are still ignored for custom providers whose auth lives inmodels.json— behavior pinned by the existing and new tests.Edge cases checked:
hasConfiguredAuthfalse path (no auth.json entry, no env var, nomodels.jsonapiKey) still hits the original fast-fail error; the message was updated to mention the custom-provider route.provider-lazy-load.test.tsstill asserts neither@mariozechner/pi-coding-agentnor@mariozechner/pi-aiare eagerly loaded when just registering and instantiating the provider; replacing theModelRegistry.inMemorycall with.createdid not change this (the call site is insidesendQuery).ModelRegistry.createis called from insidesendQueryafter thePI_PACKAGE_DIRshim is installed — same ordering contract as before; the compiled-binary smoke path is unaffected.What was not verified:
scripts/build-binaries.sh) locally — but the lazy-load test covers the critical invariant the binary build depends on (no eager Pi SDK imports).hasConfiguredAuth/findcontract is Pi's to honor, and I leaned on Pi's own tests for that.Side Effects / Blast Radius (required)
.archon/workflows/*.yamlwithprovider: pi— both chat-flowarchon-assist-style nodes and DAG nodes — inherits the new resolution path. Claude/Codex workflows untouched.~/.pi/agent/models.jsonwho was previously unaware of the file will now see a system warning. This is intentional and actionable; a hidden parse error is worse than a visible one.baseUrlorapiKeywas wrong but who never tried to use it through Archon before will now surface the underlying 401/403/DNS error at request time. Also intentional — same error surface aspiCLI.provider.pistructured logs (session_started,prompt_completed,prompt_failed) already includepiProviderandmodelId; existing log-level filtering is enough to spot regressions. The new regression test inprovider.test.tsfails loudly if someone reintroducesinMemory()or reverts topi-ai.getModel().Rollback Plan (required)
packages/providers/src/community/pi/provider.ts) plus its test. No schema, no migration, no config flag.~/.pi/agent/models.json(or use only built-in refs); both paths still work after this PR.Pi model not foundfor a ref that worked underpiCLI, or as a spuriousPi auth: no credentialserror on a custom provider whoseapiKeyis set inmodels.json. Either symptom is surfaced by the two new tests.Risks and Mitigations
ModelRegistry.create()'s default path away from~/.pi/agent/models.json.getAgentDir()(honored by bothcreate()and thepiCLI). If Pi changes the location, bothpiand Archon will pick up the new location together — same parity guarantee the PR is delivering.models.jsonentry with an unreachablebaseUrlcould stall a workflow until the request times out.requestOptions.abortSignal) still plumbs through tosession.abort(). Same failure shape aspiCLI with the samemodels.json.~/.pi/agent/models.jsonas part of onboarding could unintentionally route agent traffic through an unexpected endpoint.pialready does.Summary by CodeRabbit
New Features
Bug Fixes
Refactor