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:
@@ -253,6 +253,8 @@ pi.registerProvider("custom-api", {
|
||||
});
|
||||
```
|
||||
|
||||
The key is resolved for each request. An explicit request `Authorization` header takes precedence over the generated value.
|
||||
|
||||
## OAuth Support
|
||||
|
||||
Add OAuth/SSO authentication that integrates with `/login`:
|
||||
@@ -312,15 +314,6 @@ pi.registerProvider("corporate-ai", {
|
||||
|
||||
getApiKey(credentials: OAuthCredentials): string {
|
||||
return credentials.access;
|
||||
},
|
||||
|
||||
// Optional: modify models based on user's subscription
|
||||
modifyModels(models, credentials) {
|
||||
const region = decodeRegionFromToken(credentials.access);
|
||||
return models.map(m => ({
|
||||
...m,
|
||||
baseUrl: `https://${region}.ai.corp.com/v1`
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -330,7 +323,7 @@ After registration, users can authenticate via `/login corporate-ai`.
|
||||
|
||||
### OAuthLoginCallbacks
|
||||
|
||||
The `callbacks` object provides three ways to authenticate:
|
||||
The `callbacks` object provides UI-neutral interactions for the provider-owned flow:
|
||||
|
||||
```typescript
|
||||
interface OAuthLoginCallbacks {
|
||||
@@ -345,6 +338,9 @@ interface OAuthLoginCallbacks {
|
||||
expiresInSeconds?: number;
|
||||
}): void;
|
||||
|
||||
// Show transient progress
|
||||
onProgress?(message: string): void;
|
||||
|
||||
// Prompt user for input (for manual token entry)
|
||||
onPrompt(params: { message: string }): Promise<string>;
|
||||
|
||||
@@ -660,7 +656,6 @@ interface ProviderConfig {
|
||||
login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;
|
||||
refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;
|
||||
getApiKey(credentials: OAuthCredentials): string;
|
||||
modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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