feat(coding-agent): replace model registry with model runtime
Move provider auth and OAuth flows onto pi-ai Models, compose models.json and extension overlays through ModelRuntime, and retain ModelRegistry as an extension compatibility facade.
This commit is contained in:
@@ -16,16 +16,12 @@ See [examples/sdk/](../examples/sdk/) for working examples from minimal to full
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
// Set up credential storage and model registry
|
||||
const authStorage = AuthStorage.create();
|
||||
const modelRegistry = ModelRegistry.create(authStorage);
|
||||
import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
const modelRuntime = await ModelRuntime.create();
|
||||
const { session } = await createAgentSession({
|
||||
sessionManager: SessionManager.inMemory(),
|
||||
authStorage,
|
||||
modelRegistry,
|
||||
modelRuntime,
|
||||
});
|
||||
|
||||
session.subscribe((event) => {
|
||||
@@ -369,10 +365,9 @@ When you pass a custom `ResourceLoader`, `cwd` and `agentDir` no longer control
|
||||
|
||||
```typescript
|
||||
import { getModel } from "@earendil-works/pi-ai";
|
||||
import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
|
||||
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
const authStorage = AuthStorage.create();
|
||||
const modelRegistry = ModelRegistry.create(authStorage);
|
||||
const modelRuntime = await ModelRuntime.create();
|
||||
|
||||
// Find specific built-in model (doesn't check if API key exists)
|
||||
const opus = getModel("anthropic", "claude-opus-4-5");
|
||||
@@ -380,10 +375,10 @@ if (!opus) throw new Error("Model not found");
|
||||
|
||||
// Find any model by provider/id, including custom models from models.json
|
||||
// (doesn't check if API key exists)
|
||||
const customModel = modelRegistry.find("my-provider", "my-model");
|
||||
const customModel = modelRuntime.getModel("my-provider", "my-model");
|
||||
|
||||
// Get only models that have valid API keys configured
|
||||
const available = await modelRegistry.getAvailable();
|
||||
// Get only models that have valid authentication configured
|
||||
const available = await modelRuntime.getAvailable();
|
||||
|
||||
const { session } = await createAgentSession({
|
||||
model: opus,
|
||||
@@ -395,8 +390,7 @@ const { session } = await createAgentSession({
|
||||
{ model: haiku, thinkingLevel: "off" },
|
||||
],
|
||||
|
||||
authStorage,
|
||||
modelRegistry,
|
||||
modelRuntime,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -415,14 +409,14 @@ import {
|
||||
|
||||
const cliModel = resolveCliModel({
|
||||
cliModel: "anthropic/claude-opus-4-5:high",
|
||||
modelRegistry,
|
||||
modelRuntime,
|
||||
});
|
||||
if (cliModel.error) throw new Error(cliModel.error);
|
||||
if (cliModel.warning) console.warn(cliModel.warning);
|
||||
|
||||
const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics(
|
||||
["anthropic/*:high", "gpt-5"],
|
||||
modelRegistry,
|
||||
modelRuntime,
|
||||
);
|
||||
for (const diagnostic of diagnostics) {
|
||||
console.warn(diagnostic.message);
|
||||
@@ -435,40 +429,41 @@ for (const diagnostic of diagnostics) {
|
||||
|
||||
### API Keys and OAuth
|
||||
|
||||
API key resolution priority (handled by AuthStorage):
|
||||
Authentication resolution priority (handled by `ModelRuntime`):
|
||||
1. Runtime overrides (via `setRuntimeApiKey`, not persisted)
|
||||
2. Stored credentials in `auth.json` (API keys or OAuth tokens)
|
||||
3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
|
||||
4. Fallback resolver (for custom provider keys from `models.json`)
|
||||
|
||||
```typescript
|
||||
import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
|
||||
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
||||
import { createAgentSession, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
// Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
|
||||
const authStorage = AuthStorage.create();
|
||||
const modelRegistry = ModelRegistry.create(authStorage);
|
||||
const modelRuntime = await ModelRuntime.create();
|
||||
|
||||
const { session } = await createAgentSession({
|
||||
sessionManager: SessionManager.inMemory(),
|
||||
authStorage,
|
||||
modelRegistry,
|
||||
});
|
||||
// Provider-owned auth methods and current status
|
||||
for (const provider of modelRuntime.getProviders()) {
|
||||
const status = await modelRuntime.checkAuth(provider.id);
|
||||
console.log(provider.name, provider.auth, status);
|
||||
}
|
||||
|
||||
// Runtime API key override (not persisted to disk)
|
||||
authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
|
||||
modelRuntime.setRuntimeApiKey("anthropic", "sk-my-temp-key");
|
||||
|
||||
// Custom auth storage location
|
||||
const customAuth = AuthStorage.create("/my/app/auth.json");
|
||||
const customRegistry = ModelRegistry.create(customAuth, "/my/app/models.json");
|
||||
|
||||
const { session } = await createAgentSession({
|
||||
sessionManager: SessionManager.inMemory(),
|
||||
authStorage: customAuth,
|
||||
modelRegistry: customRegistry,
|
||||
// Custom credential and model locations
|
||||
const customRuntime = await ModelRuntime.create({
|
||||
authPath: "/my/app/auth.json",
|
||||
modelsPath: "/my/app/models.json",
|
||||
});
|
||||
|
||||
// No custom models.json (built-in models only)
|
||||
const simpleRegistry = ModelRegistry.inMemory(authStorage);
|
||||
// Or inject any pi-ai CredentialStore
|
||||
const credentials = new InMemoryCredentialStore();
|
||||
const inMemoryRuntime = await ModelRuntime.create({ credentials });
|
||||
|
||||
const { session } = await createAgentSession({
|
||||
modelRuntime: customRuntime,
|
||||
});
|
||||
```
|
||||
|
||||
> See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
|
||||
@@ -927,26 +922,22 @@ interface LoadExtensionsResult {
|
||||
import { getModel } from "@earendil-works/pi-ai";
|
||||
import { Type } from "typebox";
|
||||
import {
|
||||
AuthStorage,
|
||||
createAgentSession,
|
||||
DefaultResourceLoader,
|
||||
defineTool,
|
||||
ModelRegistry,
|
||||
ModelRuntime,
|
||||
SessionManager,
|
||||
SettingsManager,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
|
||||
// Set up auth storage (custom location)
|
||||
const authStorage = AuthStorage.create("/custom/agent/auth.json");
|
||||
|
||||
// Runtime API key override (not persisted)
|
||||
const modelRuntime = await ModelRuntime.create({
|
||||
authPath: "/custom/agent/auth.json",
|
||||
modelsPath: "/custom/agent/models.json",
|
||||
});
|
||||
if (process.env.MY_KEY) {
|
||||
authStorage.setRuntimeApiKey("anthropic", process.env.MY_KEY);
|
||||
modelRuntime.setRuntimeApiKey("anthropic", process.env.MY_KEY);
|
||||
}
|
||||
|
||||
// Model registry (no custom models.json)
|
||||
const modelRegistry = ModelRegistry.create(authStorage);
|
||||
|
||||
// Inline tool
|
||||
const statusTool = defineTool({
|
||||
name: "status",
|
||||
@@ -982,8 +973,7 @@ const { session } = await createAgentSession({
|
||||
|
||||
model,
|
||||
thinkingLevel: "off",
|
||||
authStorage,
|
||||
modelRegistry,
|
||||
modelRuntime,
|
||||
|
||||
tools: ["read", "bash", "status"],
|
||||
customTools: [statusTool],
|
||||
@@ -1149,8 +1139,8 @@ createAgentSessionRuntime
|
||||
AgentSessionRuntime
|
||||
|
||||
// Auth and Models
|
||||
AuthStorage
|
||||
ModelRegistry
|
||||
ModelRuntime // implements pi-ai Models and owns credential storage
|
||||
ModelRegistry // synchronous extension compatibility facade
|
||||
resolveCliModel
|
||||
resolveModelScopeWithDiagnostics
|
||||
|
||||
|
||||
Reference in New Issue
Block a user