mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-07-09 17:28:45 +00:00
Type name change
This commit is contained in:
parent
c3cfeac041
commit
b377623435
4 changed files with 88 additions and 44 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -240,22 +240,6 @@ export interface FileInfo {
|
|||
mtimeMs: number;
|
||||
}
|
||||
|
||||
/** Options for {@link Shell.exec}. */
|
||||
export interface ExecutionEnvExecOptions {
|
||||
/** Working directory for the command. Relative paths are resolved against {@link ExecutionEnv.cwd}. Defaults to {@link ExecutionEnv.cwd}. */
|
||||
cwd?: string;
|
||||
/** Additional environment variables for the command. Values override the environment defaults. Defaults to no overrides. */
|
||||
env?: Record<string, string>;
|
||||
/** Timeout in seconds. Implementations should return a timeout error when the command exceeds this duration. Defaults to no timeout. */
|
||||
timeout?: number;
|
||||
/** Abort signal used to terminate the command. Defaults to no abort signal. */
|
||||
abortSignal?: AbortSignal;
|
||||
/** Called with stdout chunks as they are produced. */
|
||||
onStdout?: (chunk: string) => void;
|
||||
/** Called with stderr chunks as they are produced. */
|
||||
onStderr?: (chunk: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filesystem capability used by the harness.
|
||||
*
|
||||
|
|
@ -317,12 +301,28 @@ export interface FileSystem {
|
|||
cleanup(): Promise<void>;
|
||||
}
|
||||
|
||||
/** Options for {@link Shell.exec}. */
|
||||
export interface ShellExecOptions {
|
||||
/** Working directory for the command. Relative paths are resolved against {@link ExecutionEnv.cwd}. Defaults to {@link ExecutionEnv.cwd}. */
|
||||
cwd?: string;
|
||||
/** Additional environment variables for the command. Values override the environment defaults. Defaults to no overrides. */
|
||||
env?: Record<string, string>;
|
||||
/** Timeout in seconds. Implementations should return a timeout error when the command exceeds this duration. Defaults to no timeout. */
|
||||
timeout?: number;
|
||||
/** Abort signal used to terminate the command. Defaults to no abort signal. */
|
||||
abortSignal?: AbortSignal;
|
||||
/** Called with stdout chunks as they are produced. */
|
||||
onStdout?: (chunk: string) => void;
|
||||
/** Called with stderr chunks as they are produced. */
|
||||
onStderr?: (chunk: string) => void;
|
||||
}
|
||||
|
||||
/** Shell execution capability used by the harness. */
|
||||
export interface Shell {
|
||||
/** Execute a shell command in {@link FileSystem.cwd} unless `options.cwd` is provided. */
|
||||
exec(
|
||||
command: string,
|
||||
options?: ExecutionEnvExecOptions,
|
||||
options?: ShellExecOptions,
|
||||
): Promise<Result<{ stdout: string; stderr: string; exitCode: number }, ExecutionError>>;
|
||||
/** Release shell resources. Must be best-effort and must not throw or reject. */
|
||||
cleanup(): Promise<void>;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,7 @@
|
|||
import {
|
||||
type ExecutionEnv,
|
||||
type ExecutionEnvExecOptions,
|
||||
ExecutionError,
|
||||
err,
|
||||
ok,
|
||||
type Result,
|
||||
toError,
|
||||
} from "../types.ts";
|
||||
import { type ExecutionEnv, ExecutionError, err, ok, type Result, type ShellExecOptions, toError } from "../types.ts";
|
||||
import { DEFAULT_MAX_BYTES, truncateTail } from "./truncate.ts";
|
||||
|
||||
export interface ShellCaptureOptions extends Omit<ExecutionEnvExecOptions, "onStdout" | "onStderr"> {
|
||||
export interface ShellCaptureOptions extends Omit<ShellExecOptions, "onStdout" | "onStderr"> {
|
||||
onChunk?: (chunk: string) => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { createModels } from "@earendil-works/pi-ai";
|
||||
import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
|
||||
import { cloudflareAIGatewayProvider } from "@earendil-works/pi-ai/providers/cloudflare-ai-gateway";
|
||||
import { openaiProvider } from "@earendil-works/pi-ai/providers/openai";
|
||||
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
|
||||
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.ts";
|
||||
|
|
@ -39,13 +39,20 @@ const { promptTemplates: sourcedPromptTemplates } = await loadSourcedPromptTempl
|
|||
|
||||
const models = createModels();
|
||||
models.setProvider(openaiProvider());
|
||||
models.setProvider(cloudflareAIGatewayProvider());
|
||||
const model = models.getModel("openai", "gpt-5.5");
|
||||
// const model = models.getModel("cloudflare-ai-gateway", "claude-haiku-4-5");
|
||||
if (!model) {
|
||||
console.log("Model not found");
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
const session = new Session(new InMemorySessionStorage());
|
||||
const agent = new AgentHarness({
|
||||
env,
|
||||
session,
|
||||
models,
|
||||
model: getBuiltinModel("openai", "gpt-5.5"),
|
||||
model,
|
||||
thinkingLevel: "low",
|
||||
systemPrompt: ({ env, resources }) =>
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue