Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,26 @@

## [Unreleased]

### Breaking Changes

- Replaced Atomic's legacy extension OAuth registration bridge with provider-owned authentication from `@earendil-works/pi-ai`. Extensions must declare OAuth or API-key authentication on their provider registration. The package root no longer exports the bridge functions `registerOAuthProvider`, `resetOAuthProviders`, `getOAuthApiKey`, `getOAuthProvider`, or `getOAuthProviders`, nor the bridge and credential/status types `LegacyOAuthProvider`, `OAuthProviderDescriptor`, `ApiKeyCredential`, `AuthCredential`, `AuthStatus`, or `OAuthCredential`; import current credential types from `@earendil-works/pi-ai` and use provider-owned authentication instead. The internal legacy registration/refresh machinery and custom API-key login hooks beyond pi's provider contract were also removed.
- Model configuration now follows pi's single-file `ModelConfig` contract. Atomic reads one `models.json` from the active Atomic agent directory (`~/.atomic/agent/models.json`, or the directory selected by `ATOMIC_CODING_AGENT_DIR`/`PI_CODING_AGENT_DIR`); it no longer reads project-scoped `.atomic/models.json`, falls back to `~/.pi/agent/models.json`, or layers and merges `.pi` and `.atomic` model configuration files. Move project-scoped custom providers and models into the active agent-directory file. The legacy `.pi` read fallback remains only for configuration surfaces that explicitly use layered paths, such as `auth.json`.
- Model-catalog refreshes now use pi's exact timeout semantics. `modelRefreshTimeoutMs` applies only to the initial runtime creation refresh, and post-login catalog refreshes are unbounded; the interactive `/model` selector owns its 15-second refresh timeout like pi, rendering cached models immediately, aborting a slow refresh, and reporting "Model refresh timed out; showing cached models." Callers of `ModelRuntime.refresh()` that require cancellation must provide their own abort signal.
- Extension `streamSimple` implementations are now scoped to their registered provider and `ModelRuntime`, matching pi's provider composer. Unregistering a newer provider no longer restores an older global API-owner registration; extensions that replace a provider stream must keep that provider registered for as long as the stream should remain active.
- Remote model catalogs now publish refreshed models in memory before persisting the catalog, matching pi's ordering. If the catalog-store write fails, refresh reports the storage error but the newly refreshed in-memory catalog remains active for the current process.
- `AuthStorage` now implements pi's asynchronous `CredentialStore` contract directly. Its synchronous compatibility methods (`get`, `set`, `remove`, `has`, `hasAuth`, `getAll`, `getAuthStatus`, `getLoadError`, `drainErrors`, and synchronous `list`), `asCredentialStore()` adapter, fallback/runtime-key helpers (`setFallbackResolver`, `getRuntimeApiKey`, `setRuntimeApiKey`, and `removeRuntimeApiKey`), and provider-auth methods (`login`, `logout`, `logoutAsync`, `getModelAuth`, `getApiKey`, and `getOAuthProviders`) were removed. Use and `await` `read`, `list`, `modify`, and `delete` for persisted credentials, call `reload` when an existing instance must reread its backing store, and use `ModelRuntime` for authentication resolution, login/logout, and runtime API-key overrides.
- `ModelRegistry` is now constructed from a `ModelRuntime` and exposes only pi's thin compatibility-facade surface. The `create` and `inMemory` factories, public `authStorage` member, and the `canRestoreUnknownModel`, `checkAuth`, `getAuth`, `getCustomApiKeyAuth`, `getCustomApiKeyAuthProviders`, `getProviders`, `hasProvider`, `hasRegisteredStreamSimpleForApi`, `login`, and `logoutProvider` methods were removed. Instantiate and consume `ModelRuntime` for runtime-owned model/provider discovery and authentication, and pass it to `new ModelRegistry(runtime)` only where the synchronous extension-facing facade is required.
- `CreateAgentSessionOptions` no longer accepts the public `authStorage` and `modelRegistry` overrides. Construct or supply a `ModelRuntime` through `modelRuntime` instead; when omitted, `createAgentSession()` creates the runtime from the active agent directory's `auth.json` and `models.json`.

### Changed

- Adopted pi's provider-owned `ModelRuntime` architecture for model composition, credentials, streaming, and catalog refresh. `ModelRegistry` is now the thin synchronous extension-compatibility facade used by pi, while coding-agent, SDK, RPC, isolated-engine, workflow, and MCP internals consume `ModelRuntime` directly.

### Fixed

- Fixed OAuth logins being destroyed by an unrelated model-catalog refresh, matching upstream pi's behavior. After a successful `/login`, a timed-out (aborted) or partially failed catalog refresh threw `Model refresh aborted after OAuth login` and rolled the freshly acquired tokens back to the previous credential. Because providers rotate refresh tokens, that rollback could permanently strand a server-side-invalidated credential — every send then failed with `invalid_grant` ("Refresh token not found or invalid") and every re-login was rolled back again, typically on machines with slow routes to catalog endpoints. Freshly persisted OAuth credentials now always survive the post-login refresh: per-provider refresh errors and refresh timeouts no longer fail the login in either the direct interactive or isolated-engine path, and models fall back to the cached snapshot.
- Fixed the `/model` selector reporting `Could not refresh llama.cpp; showing cached models.` for users who never configured a llama.cpp server. The bundled llama.cpp extension now uses pi's provider-owned registration: the provider stays dormant — no refresh attempt, no error — until a server is configured through `LLAMA_BASE_URL` or a stored login, and `/login` prompts for the server URL plus optional API key exactly like pi.
- Fixed RPC `save_provider_credential` writes disappearing after process restart. Saved API-key and OAuth credentials now persist to `auth.json` through `ModelRuntime.saveCredential()` instead of being stored as non-persistent runtime API-key overrides; the RPC command again accepts the full credential union, awaits a model-catalog refresh, and returns the refreshed catalog.

## [0.9.11-alpha.7] - 2026-07-28

Expand Down
8 changes: 3 additions & 5 deletions packages/coding-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,14 +429,12 @@ See [docs/packages.md](docs/packages.md).
### SDK

```typescript
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@bastani/atomic";
import { createAgentSession, SessionManager } from "@bastani/atomic";

const authStorage = AuthStorage.create();
const modelRegistry = ModelRegistry.create(authStorage);
// By default, createAgentSession builds a ModelRuntime from the active agent
// directory's auth.json and models.json.
const { session } = await createAgentSession({
sessionManager: SessionManager.inMemory(),
authStorage,
modelRegistry,
});

await session.prompt("What files are in the current directory?");
Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/docs/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ See [SDK](/sdk) and [Custom providers](/custom-provider).
## Pi 0.80.6 compatibility

- **Synced through upstream Pi 0.80.6.** Atomic and its bundled extensions now use the 0.80.6 Pi runtime packages, including signed empty Anthropic thinking preservation, request-wide GPT-5.4/5.5 long-context pricing, and corrected GPT-5.6 catalog/backend metadata.
- **Model controls are current.** Atomic accepts `max` wherever the active model advertises it, applies `models.json` overrides to matching extension-registered models and built-ins, and loads legacy `.pi` plus primary `.atomic` layers during normal CLI startup. Disjoint override IDs survive, exact primary entries replace complete legacy entries, and removed catalog IDs fall back instead of being synthesized on resume. See [Models](/models).
- **Model controls are current.** Atomic accepts `max` wherever the active model advertises it, applies the active agent directory's single `models.json` overrides to matching extension-registered models and built-ins, and falls back instead of synthesizing removed catalog IDs on resume. See [Models](/models).
- **Custom pricing tiers are preserved.** Custom `models.json` entries and extension providers can declare complete request-wide `cost.tiers`; matching `modelOverrides` can update scalar rates without losing inherited tiers or replace/clear the tier array explicitly. See [Models](/models#request-wide-cost-tiers) and [Custom Providers](/custom-provider#usage-and-cost).
- **Safer session and tool behavior.** Invalid explicit bash timeouts fail instead of being clamped; missing exact session IDs warn before creation; lax null or omitted message content is normalized; auth writes surface persistence failures; Windows context traversal terminates at drive roots; and duplicate fork selections are ignored. See [Tools](/tools) and [Usage](/usage).
- **More reliable streaming and binaries.** Visible custom messages stay before the live assistant row, standalone Linux clipboard reads can fall back to xclip with correctly packaged native bindings, caller-relative `TMPDIR` paths work from external directories, and `--skip-deps` tolerates a missing optional clipboard wrapper.
Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/docs/custom-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ After registration, users can authenticate via `/login corporate-ai`.

Existing extension OAuth definitions keep their `login`, `refreshToken`, `getApiKey`, and optional `modifyModels` methods. OAuth refresh is serialized so concurrent requests do not overwrite each other's credentials.

In isolated interactive mode, extension code and executable OAuth methods remain in the engine process. Atomic transports only the JSON-safe provider description (`id`, `name`, `loginLabel`, and `usesCallbackServer`) to the terminal process; it never serializes provider functions or acquired credentials and does not load the extension a second time in the frontend. The engine executes the provider's login closure and correlates browser URLs, device codes, progress/info messages, prompts, selections, and manual-code callbacks with the originating login.
In isolated interactive mode, extension code and executable OAuth methods remain in the engine process. Atomic transports only the JSON-safe provider description (`id`, `name`, `loginLabel`, and `usesCallbackServer`) to the terminal process; it never serializes provider functions or acquired credentials and does not load the extension a second time in the frontend. `loginLabel` replaces the login dialog title, while `usesCallbackServer: true` exposes a redirect-URL paste field that races the browser callback. The engine executes the provider's login closure and correlates browser URLs, device codes, progress/info messages, prompts, selections, and manual-code callbacks with the originating login.

After acquisition, the engine owns serialized credential persistence, model/current-model refresh, rollback on failure, and logout. The frontend applies the returned catalog only after the engine transaction succeeds. Escape or Ctrl+C cancels only the matching login and leaves the prior credential/catalog intact. Built-in OAuth and direct, non-isolated extension OAuth use the same persistence and cancellation semantics; later provider registrations continue to override earlier registrations by ID.

Expand Down
10 changes: 4 additions & 6 deletions packages/coding-agent/docs/models.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Custom Models

Add custom providers and models (Ollama, vLLM, LM Studio, proxies) via `~/.atomic/agent/models.json` (legacy `~/.pi/agent/models.json` is also read).

When both files exist, Atomic reads the legacy `.pi` file first and the primary `.atomic` file second. For `modelOverrides`, entries are layered by provider and model ID: disjoint legacy entries remain available, while an exact primary provider/model entry replaces the complete legacy override entry. Atomic does not field-merge one override entry across files; use `{}` in the primary file to restore the built-in model values for that exact entry.
Add custom providers and models (Ollama, vLLM, LM Studio, proxies) via the single `models.json` in the active Atomic agent directory, normally `~/.atomic/agent/models.json`, or the directory selected by `ATOMIC_CODING_AGENT_DIR`/`PI_CODING_AGENT_DIR`. Atomic reads only that file: it does not read project-scoped `.atomic/models.json`, fall back to `~/.pi/agent/models.json`, or merge `.pi` and `.atomic` model configuration files. The legacy `.pi` read fallback remains available for configuration surfaces that explicitly use layered config paths, such as `auth.json`; it does not apply to `models.json`.

A complete `defaultProvider`/`defaultModel` pair in `settings.json` is resolved after built-in, configured, and extension providers register. If the provider remains unsupported, interactive mode reports a generic saved-configuration warning and leaves model selection open instead of routing the session to a different provider. Print and JSON modes write that diagnostic to stderr and exit nonzero before prompting, keeping JSON stdout JSONL-clean. RPC rejects `prompt` with the same correlated diagnostic until an explicit successful `set_model` selects an available model or an explicit model cycle returns a different available model. A null or unchanged cycle result does not clear the condition. If the provider is supported but the model is unknown or lacks authentication, normal automatic selection of an available authenticated model continues. Valid custom- and extension-provider defaults resolve once their provider registration is available. See [Settings](/settings#model--thinking).

Expand Down Expand Up @@ -95,7 +93,7 @@ Override defaults when you need specific values:
}
```

Atomic reloads every configured `models.json` layer each time you open `/model`: legacy global/project `.pi` sources first, then primary global/project `.atomic` sources. Provider definitions, complete per-model overrides, dynamic catalogs, and isolated-engine model state are rebuilt from that fresh layered view, so edits take effect without restarting. Invalid edits report an error and do not silently reuse a different layer.
Atomic reloads the active agent directory's single `models.json` each time you open `/model`. Provider definitions, per-model overrides, dynamic catalogs, and isolated-engine model state are rebuilt from that fresh configuration, so edits take effect without restarting. Invalid edits report an error.

## Google AI Studio Example

Expand Down Expand Up @@ -418,13 +416,13 @@ Use `modelOverrides` to customize specific models without replacing the provider

`modelOverrides` supports these fields per model: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial scalar rates plus optional full tier-array replacement), `contextWindow`, `maxTokens`, `headers`, `compat`.

When both `~/.pi/agent/models.json` and `~/.atomic/agent/models.json` define `modelOverrides`, Atomic merges their nested provider/model maps in that order. Different model IDs survive from both files. For the same provider and model ID, the primary `.atomic` entry replaces the entire legacy `.pi` override entry rather than deep-merging individual fields. This complete-entry rule includes `headers`: a primary exact override without headers removes headers that came from the legacy override, but does not erase a surviving custom model definition's own headers. An empty primary override (`{}`) therefore restores the model's built-in values for that entry.
Atomic reads one `models.json` from the active agent directory. It does not layer model overrides from `.pi` and `.atomic` files.

Within a single file, custom model definitions replace matching built-in entries after built-in overrides are applied. `modelOverrides` composes only with built-in and extension-registered models; it does not modify a same-ID custom model definition.

Behavior notes:
- Atomic retains the parsed override map even when an extension registers the matching provider/model after `models.json` is loaded.
- Layered primary/legacy compatibility merges override maps by provider and model ID; disjoint entries survive, while a primary exact entry replaces the complete legacy entry without cross-file field-level merging.
- Model overrides come from the active agent directory's single `models.json`; no cross-file layering or merging is performed.
- For matching built-in and extension-registered models, the model definition is the base and `modelOverrides` wins configured fields. Extension-registered model headers are shallow-merged with override headers, with override headers winning duplicate names. A same-ID custom model replaces the built-in override result, including its complete header record.
- A scalar-only `cost` override preserves inherited tiers. Supplying `cost.tiers` replaces the complete tier array, including `[]` to clear it; omitted scalar cost fields remain inherited.
- Provider-level request headers remain a separate provider layer and are combined at request time.
Expand Down
Loading
Loading