|
|
|
@@ -737,9 +737,10 @@ Rules:
|
|
|
|
|
3. Lazy API wrappers dynamically import real API implementations.
|
|
|
|
|
4. Real API implementations import SDK dependencies.
|
|
|
|
|
5. OAuth implementations are always attached via `lazyOAuth()` and lazy-loaded behind a bundler-opaque dynamic import; provider metadata never eagerly imports Node-only OAuth code.
|
|
|
|
|
6. `providers/all` may import all provider metadata, but no eager SDK imports.
|
|
|
|
|
6. `providers/all` imports every built-in provider factory and all catalogs. It is the explicit heavy entrypoint.
|
|
|
|
|
7. Provider modules are side-effect-free; importing a provider does not register anything globally.
|
|
|
|
|
8. `package.json` sets `sideEffects: false`.
|
|
|
|
|
8. `package.json` lists only effectful compat/image registration files in `sideEffects`; root and provider modules stay tree-shakeable.
|
|
|
|
|
9. With code splitting, provider SDKs stay in lazy chunks. Without code splitting, bundlers fold statically reachable lazy API implementations into the single bundle; `providers/all` then pulls all statically visible SDKs. Bedrock is the exception because its AWS SDK implementation is behind a bundler-opaque Node-only import and needs `setBedrockProviderModule()` for standalone single-file bundles.
|
|
|
|
|
|
|
|
|
|
Exports map sketch:
|
|
|
|
|
|
|
|
|
@@ -786,11 +787,12 @@ for (const provider of layeredProviders) sessionModels.setProvider(provider);
|
|
|
|
|
|
|
|
|
|
coding-agent owns: `FileCredentialStore` + decorators replacing AuthStorage (see "Replacing AuthStorage"), models.json auth sidecar (`$ENV`, `!command`), command execution policy, provider status labels (from `AuthResult.source`), login/logout UI (driving `auth.{apiKey,oauth}.login()` with `prompt()/notify()`), extension lifecycle, provider-management slash commands.
|
|
|
|
|
|
|
|
|
|
Until then, the only coding-agent changes in this pass are:
|
|
|
|
|
Current interim state:
|
|
|
|
|
|
|
|
|
|
- construct a `Models` instance for `AgentHarness` (builtins + legacy api-dispatch fallback bridging `ModelRegistry` custom providers)
|
|
|
|
|
- switch old-global imports to `@earendil-works/pi-ai/compat`
|
|
|
|
|
- adapt the login dialog to `prompt()/notify()` callbacks (thin UI adapter; replaces the `usesCallbackServer` special-casing)
|
|
|
|
|
- `AgentHarness` already accepts a `Models` instance and uses it for turn streaming, compaction, and branch summaries.
|
|
|
|
|
- coding-agent does not use `AgentHarness` yet; `AgentSession` still drives the low-level `Agent` with a `streamFn`.
|
|
|
|
|
- coding-agent still uses legacy `AuthStorage` + `ModelRegistry` and imports old global pi-ai APIs through `@earendil-works/pi-ai/compat`.
|
|
|
|
|
- The extension loader still aliases the pi-ai root to `/compat` as the runtime grace period for old extensions.
|
|
|
|
|
|
|
|
|
|
## Implementation TODOs
|
|
|
|
|
|
|
|
|
@@ -877,16 +879,59 @@ Decisions:
|
|
|
|
|
- Copilot: stored-credential baseUrl applied in the wrapped `getModels()` (extension-visible models stay correct) plus per-request `toAuth().baseUrl`.
|
|
|
|
|
- Cloudflare: provider-auth substitution (key + `CLOUDFLARE_ACCOUNT_ID`/`CLOUDFLARE_GATEWAY_ID` from credential metadata/env -> `ModelAuth.baseUrl`). Built-in compat calls route through `Models`, so they use the same provider auth path.
|
|
|
|
|
|
|
|
|
|
Ordering:
|
|
|
|
|
Ordering for new sessions:
|
|
|
|
|
|
|
|
|
|
- [x] pi-ai rework first: `Provider.getModels()` sync + optional `refreshModels()`; `Models.getModels`/`getModel` sync, `Models.refresh(provider?)` async; `createProvider` takes `models` array + optional `refreshModels` fetcher (in-flight dedupe). Reverses Phase 1's async-listing decision — see "Provider model listing" for rationale (sync-or-async unions breed latent sync assumptions; async-only breaks sync consumer surfaces like extension `find`/`getAll`).
|
|
|
|
|
- [ ] `FileCredentialStore` (ports the auth.json lock backend, reads legacy `type: "api_key"` tags) + `--api-key` overlay + `$ENV`/`!command` resolution; tests.
|
|
|
|
|
- [x] Cloudflare provider auth in pi-ai factories.
|
|
|
|
|
- [ ] Copilot `getModels` baseUrl wrap.
|
|
|
|
|
- [ ] Extension-OAuth adapter (old `OAuthProviderInterface` config -> `OAuthAuth`).
|
|
|
|
|
- [ ] ModelRegistry rebuild: owns `MutableModels`, async reads, models.json decoration, provider-keyed extension streams, facade auth ops; AuthStorage deleted.
|
|
|
|
|
- [ ] Consumer rewiring: agent-session, sdk (`credentials?: CredentialStore` option replaces `authStorage`; sdk.md updated), model-resolver, interactive login/status UI on `prompt()/notify()`, cli `--api-key`.
|
|
|
|
|
- [ ] Test migration; tmux validation of login flows against real providers.
|
|
|
|
|
1. [x] pi-ai rework first: `Provider.getModels()` sync + optional `refreshModels()`; `Models.getModels`/`getModel` sync, `Models.refresh(provider?)` async; `createProvider` takes `models` array + optional `refreshModels` fetcher (in-flight dedupe). Reverses Phase 1's async-listing decision — see "Provider model listing" for rationale (sync-or-async unions breed latent sync assumptions; async-only breaks sync consumer surfaces like extension `find`/`getAll`).
|
|
|
|
|
2. [x] Cloudflare provider auth in pi-ai factories: Workers AI and AI Gateway validate their required account/gateway metadata and return resolved `baseUrl`, provider-scoped env, and header suppression/override metadata from provider auth.
|
|
|
|
|
3. [ ] Add `FileCredentialStore` in coding-agent.
|
|
|
|
|
- Implement the pi-ai `CredentialStore` interface over the existing `auth.json` lock backend (`FileAuthStorageBackend` / `InMemoryAuthStorageBackend` can be reused or renamed).
|
|
|
|
|
- Preserve the existing file format where possible, but normalize legacy `{ type: "api_key", key, env? }` to pi-ai `{ type: "api-key", key, metadata? }` on read. `env` becomes provider metadata/env sidecar data; do not lose unknown keys.
|
|
|
|
|
- `read(provider)` returns the current credential snapshot and records parse/storage errors for status UI parity.
|
|
|
|
|
- `modify(provider, fn)` must lock, re-read, run `fn`, merge-write the provider entry, chmod `0600`, and return the post-write credential.
|
|
|
|
|
- `delete(provider)` must lock and remove only that provider's entry.
|
|
|
|
|
- Add file-backed and in-memory tests covering lock/RMW behavior, legacy `api_key` reads, OAuth reads, metadata/env preservation, delete, parse errors, and concurrent refresh-style modifications.
|
|
|
|
|
4. [ ] Add store decorators for coding-agent policy.
|
|
|
|
|
- `withConfigValues(store, policy)` resolves stored API-key credentials whose `key` or metadata values use `$ENV` or `!command`, using existing `resolve-config-value.ts` semantics. Command execution stays in coding-agent, not pi-ai.
|
|
|
|
|
- `withRuntimeOverrides(store, overrides)` implements CLI `--api-key`: read returns an ephemeral `{ type: "api-key", key }` for each overridden provider, masking stored OAuth/API credentials without persisting.
|
|
|
|
|
- Runtime overrides must apply even to OAuth-capable providers; every provider registered in coding-agent must retain or gain an `apiKey` auth slot so the overlay is meaningful.
|
|
|
|
|
- Tests cover precedence: runtime override > stored credential > models.json config auth > ambient provider env, with stored credential blocking ambient fallback.
|
|
|
|
|
5. [ ] Build provider decoration helpers for `models.json`.
|
|
|
|
|
- Start from built-in provider factories, not generated model arrays.
|
|
|
|
|
- Wrap provider `getModels()` so provider-level `baseUrl`/`headers`/`compat`, per-model `modelOverrides`, and custom model merges apply on every sync read.
|
|
|
|
|
- Preserve `refreshModels()` passthrough so dynamic providers compose with decorations.
|
|
|
|
|
- Convert provider `apiKey`/`headers`/`authHeader` models.json config into a wrapped `ApiKeyAuth` that resolves config values first and falls back to the base provider auth.
|
|
|
|
|
- Custom providers with `models` use `createProvider()` with the appropriate lazy API wrapper or extension-provided stream implementation.
|
|
|
|
|
- Parse errors must keep current `ModelRegistry.getError()` behavior: built-ins remain available, and the error is visible.
|
|
|
|
|
6. [ ] Copilot `getModels()` baseUrl wrap.
|
|
|
|
|
- GitHub Copilot OAuth `toAuth()` already returns per-credential request `baseUrl` for streaming.
|
|
|
|
|
- Wrap Copilot's provider `getModels()` when an OAuth credential is present so extension/UI-visible model metadata also carries the authenticated account base URL.
|
|
|
|
|
- Keep API-key/env-token Copilot behavior unchanged.
|
|
|
|
|
- Add tests for model metadata before login, after OAuth credential, after refresh/baseUrl change, and logout.
|
|
|
|
|
7. [ ] Extension OAuth adapter.
|
|
|
|
|
- Adapt old extension `OAuthProviderInterface` configs to pi-ai `OAuthAuth`.
|
|
|
|
|
- `login` maps old callbacks/events to `prompt()/notify()`.
|
|
|
|
|
- `refreshToken` maps to `refresh`.
|
|
|
|
|
- `getApiKey` maps to `toAuth`.
|
|
|
|
|
- `modifyModels` becomes a provider `getModels()` wrapper plus `toAuth().baseUrl` where applicable.
|
|
|
|
|
- Preserve existing extension runtime compatibility through the `/compat` alias until Phase 10.
|
|
|
|
|
8. [ ] Rebuild coding-agent `ModelRegistry` over `MutableModels`.
|
|
|
|
|
- It owns a `MutableModels` instance built from decorated built-ins + models.json custom providers + extension providers.
|
|
|
|
|
- `getAll()`, `find()`, and `getAvailable()` become async and delegate to the collection/status checks. Update extension-facing types and changelog this breaking change.
|
|
|
|
|
- `refresh()` rebuilds provider layers and calls `models.refresh()` where needed; no global api-registry reset should be part of the new path except compat-only grace behavior.
|
|
|
|
|
- `registerProvider()`/`unregisterProvider()` mutate provider layers and rebuild the collection.
|
|
|
|
|
- Facade auth ops (`login`, `logout`, provider status, available OAuth providers) drive `provider.auth.{apiKey,oauth}` and the `CredentialStore`; no `AuthStorage` type remains.
|
|
|
|
|
- Legacy `registerApiProvider` writes stay only for `/compat` callers and are removed in Phase 10.
|
|
|
|
|
9. [ ] Rewire consumers.
|
|
|
|
|
- `AgentSession` stream function resolves through `ModelRegistry`/`Models`, not `getApiKeyAndHeaders()` + compat globals.
|
|
|
|
|
- SDK options replace `authStorage` with `credentials?: CredentialStore` or an agent-dir-backed default; update `sdk.md` and examples.
|
|
|
|
|
- `model-resolver`, `--list-models`, model selector, login/logout/status UI, and provider attribution await async model/auth APIs.
|
|
|
|
|
- CLI `--api-key` populates the runtime override decorator instead of mutating `AuthStorage`.
|
|
|
|
|
- Keep extension loader root-to-compat alias until Phase 10, but expose the new collection/facade as the forward API.
|
|
|
|
|
10. [ ] Test migration and real-provider validation.
|
|
|
|
|
- Unit tests for `FileCredentialStore`, config-value decorators, provider decoration, extension OAuth adapter, ModelRegistry async facade, and consumer rewiring.
|
|
|
|
|
- Regression tests for Cloudflare account/gateway metadata, Copilot OAuth baseUrl wrapping, runtime `--api-key` precedence, `$ENV`/`!command` resolution, and stored credential blocking ambient fallback.
|
|
|
|
|
- Update existing tests that assume sync `ModelRegistry.getAll/find/getAvailable`.
|
|
|
|
|
- Run targeted non-e2e suites plus tmux validation of login flows against real providers (Anthropic OAuth/API key, OpenAI Codex OAuth, GitHub Copilot OAuth, Cloudflare AI Gateway, Bedrock if credentials are available).
|
|
|
|
|
|
|
|
|
|
### Phase 10 — compat deletion (pi 2.0 era, separate)
|
|
|
|
|
|
|
|
|
|