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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **ChatGPT provider.** `provider.module: "openai-chatgpt"` is now a valid host-config value,
backed by `amplifier-module-provider-openai-chatgpt`. It uses a ChatGPT Plus/Pro/Team
subscription as the backend instead of a per-token API key, talking to the ChatGPT backend
(Codex API) rather than the public OpenAI API. Default model is `gpt-5.5`.
Auth is OAuth device-code, not an environment variable: the provider module drives an
interactive login at mount time (`login_on_mount`, default true) and caches tokens to
`~/.amplifier/openai-chatgpt-oauth.json`, refreshing them itself. Requires "Sign in with
device code" enabled in the account's ChatGPT Security settings. Like `github-copilot`,
`auth set openai-chatgpt` is refused — there is no static key to store.

## [0.12.0] — 2026-07-29

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Public integrations run opencode, paperclip, and NanoClaw on it: see [who has in

`amplifier-agent` ships with:

- Five providers behind one interface: Anthropic, OpenAI, Azure OpenAI, Ollama, and GitHub Copilot, with credentials read from the environment
- Six providers behind one interface: Anthropic, OpenAI, Azure OpenAI, Ollama, GitHub Copilot, and ChatGPT (a Plus/Pro/Team subscription via OAuth device-code, no API key), with credentials read from the environment or a cached OAuth session
- Role-based model routing, so a sub-agent gets a model matched to its job rather than the frontier model for everything, re-matched when you switch providers
- Context management that keeps long sessions running, compacting history before it overruns the window
- Tools for filesystem, bash, web, search, todo, and MCP
Expand Down
8 changes: 8 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Provider is auto-detected from environment variables in this precedence:
3. `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_ENDPOINT`
4. `OLLAMA_HOST` (defaults to `http://localhost:11434`)

`github-copilot` and `openai-chatgpt` are excluded from this auto-detect chain -- neither resolves
from a single API-key environment variable. `github-copilot` reads its own token chain from the
environment (see below). `openai-chatgpt` has no credential env var at all: it authenticates via
OAuth device-code, caching tokens to `~/.amplifier/openai-chatgpt-oauth.json`. Both must be
selected explicitly with `provider.module` in a host config file.

Override by passing `--config <path>` at a host config file that names a provider explicitly.

> **Deprecated alias:** `AZURE_OPENAI_KEY` (without `_API_`) is still accepted as a fallback for backwards compatibility and triggers a one-time stderr warning when used. Prefer `AZURE_OPENAI_API_KEY`. The legacy name will be removed in a future release.
Expand Down Expand Up @@ -58,6 +64,8 @@ Resolution is **env-first**, so existing shell-rc workflows keep working unchang
This matters for hosts that spawn `amplifier-agent` as a subprocess: once you have run `auth set` a single time, every subsequent invocation picks the key up automatically, from any terminal, from any directory, with or without exported environment variables.

> **`github-copilot` is environment-only.** The other providers receive their credential through the mount config, so `auth set` works for them. The Copilot provider reads its token directly from the environment and ignores the config value, so `auth set github-copilot` is refused rather than storing a token the provider can never see. Set one of these instead (first non-empty wins): `COPILOT_AGENT_TOKEN`, `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN`.

> **`openai-chatgpt` has no static key to store, so `auth set openai-chatgpt` is also refused.** It authenticates via OAuth device-code instead: the provider module drives an interactive login at mount time (`login_on_mount`, default true) and caches tokens to `~/.amplifier/openai-chatgpt-oauth.json`, refreshing them itself. Requires "Sign in with device code" enabled in the account's ChatGPT Security settings.
>
> ```bash
> export GITHUB_TOKEN=$(gh auth token)
Expand Down
2 changes: 1 addition & 1 deletion docs/INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The engine runs **one turn per invocation** and exits. Continuity across turns c

`amplifier-agent` is a standalone binary. You do not need the Amplifier CLI, bundles, or any other repository in the `microsoft/amplifier*` family, and none of them is a substitute for it here.

Use it when your software needs to run an agent: a loop with tools, file access, sub-agents, and/or multi-turn state. It also works for plain LLM calls, where you get routing across five providers behind one interface.
Use it when your software needs to run an agent: a loop with tools, file access, sub-agents, and/or multi-turn state. It also works for plain LLM calls, where you get routing across six providers behind one interface.

Then pick a surface below, install the engine ([INSTALL.md](INSTALL.md)), and finish with the [checklist](#checklist-for-a-new-integration).

Expand Down
2 changes: 1 addition & 1 deletion docs/LAYERS_AND_RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ The engine ships with `bundle.md` baked into the wheel. It declares which module

**Pre-wired modules:**

- **Providers:** `provider-anthropic`, `provider-openai`, `provider-azure-openai`, `provider-ollama`, `provider-github-copilot`
- **Providers:** `provider-anthropic`, `provider-openai`, `provider-azure-openai`, `provider-ollama`, `provider-github-copilot`, `provider-openai-chatgpt`
- **Orchestrator:** `loop-streaming` (with `extended_thinking: true`)
- **Context:** `context-simple` (300K tokens, auto-compact at 80%)
- **Tools:** `tool-filesystem`, `tool-bash`, `tool-web`, `tool-search`, `tool-todo`, `tool-apply-patch`, `tool-delegate`, `tool-mcp`, `tool-skills`, `tool-mode`, `tool-recipes`
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/architecture.dot
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ digraph amplifier_agent {

node [fillcolor="#d9d9d9"];
kernel [label="amplifier-foundation\nbundle / session kernel"];
providers [label="LLM providers\nanthropic · openai · azure\nollama · copilot"];
providers [label="LLM providers\nanthropic · openai · azure\nollama · copilot · chatgpt"];
mcp [label="MCP servers"];
}

Expand Down
2 changes: 1 addition & 1 deletion docs/spec/bundle-and-cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ default_provider: anthropic REQUIRED, engine-level, top-level key

providers: install-only stubs, no config and no credentials
provider-anthropic, provider-openai, provider-azure-openai,
provider-ollama, provider-github-copilot
provider-ollama, provider-github-copilot, provider-openai-chatgpt

session.orchestrator: loop-streaming extended_thinking: true
session.context: context-simple max_tokens 300000, auto_compact
Expand Down
3 changes: 2 additions & 1 deletion docs/spec/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ serve restart
auth set PROVIDER [API_KEY] [--stdin] [--endpoint URL]
Writes ~/.amplifier-agent/credentials.json (mode 0600, atomic write). --stdin reads the key
from stdin so it never appears in argv. --endpoint carries an Azure-style deployment URL.
`github-copilot` is refused: it reads its token from the environment.
`github-copilot` is refused: it reads its token from the environment. `openai-chatgpt` is
also refused: it has no static key, authenticating instead via OAuth device-code.

auth list
Per-provider table: masked value plus source (`env=<VAR>` / `file` / `default` / `not set`).
Expand Down
4 changes: 2 additions & 2 deletions docs/spec/host-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ approval.patterns must be a list of strings
`provider` selects the provider module and carries its config.

```
provider.module one of: anthropic, openai, azure-openai, ollama, github-copilot
provider.module one of: anthropic, openai, azure-openai, ollama, github-copilot, openai-chatgpt
provider.config free-form; belongs to the provider module
```

Expand All @@ -101,7 +101,7 @@ module config. Closed per-entry schema:
}
```

`module` defaults to the entry's own id when omitted and must be one of the five valid module names.
`module` defaults to the entry's own id when omitted and must be one of the six valid module names.
`config` must be an object. Unknown keys inside an entry raise `config_unknown_key`. An empty
`providers` object passes validation; HTTP startup rejects it separately at boot so single-turn mode
never trips on a stale block.
Expand Down
23 changes: 17 additions & 6 deletions docs/spec/providers-and-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ routes a wire `model` field to a provider (see `http-face.md`).

## Supported providers

Five providers are supported, and only five. The provider name is the value used in configuration,
Six providers are supported, and only six. The provider name is the value used in configuration,
in `auth` subcommands, and in `models list --provider`.

```
Expand All @@ -18,10 +18,11 @@ openai provider-openai
azure-openai provider-azure-openai
ollama provider-ollama
github-copilot provider-github-copilot
openai-chatgpt provider-openai-chatgpt
```

Each module is installed from `git+https://github.com/microsoft/amplifier-module-<module>@main`.
All five are declared by the shipped bundle as install-only, so preparing the bundle makes every
All six are declared by the shipped bundle as install-only, so preparing the bundle makes every
provider importable before any session exists.

The agent holds no static table of default models, credential field shapes, or display names. Those
Expand All @@ -48,6 +49,7 @@ openai OPENAI_API_KEY
azure-openai AZURE_OPENAI_API_KEY, then AZURE_OPENAI_KEY
ollama OLLAMA_HOST, then OLLAMA_BASE_URL
github-copilot GITHUB_TOKEN
openai-chatgpt (none -- OAuth device-code)
```

`AZURE_OPENAI_KEY` is the only deprecated alias. Consulting it emits a one-time warning on stderr.
Expand All @@ -61,6 +63,11 @@ github-copilot lists only `GITHUB_TOKEN` here. The provider module resolves its
(`COPILOT_AGENT_TOKEN`, `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN`); listing those here
would mark them deprecated, which they are not.

openai-chatgpt has no environment variable at all. It resolves from a cached OAuth token file
(`~/.amplifier/openai-chatgpt-oauth.json`), written by the provider module's own device-code login
flow (`login_on_mount`) and refreshed automatically thereafter. Its resolution reports source
`"file"` when a token is cached and `"none"` otherwise -- never `"env"`.

A resolution reports the provider, whether it resolved, the source (`env`, `file`, `default`, or
`none`), the variable consulted, and the resolved fields. Ollama backed only by the built-in default
host reports unresolved on purpose, so auto-enrollment does not enlist a local daemon that may not
Expand Down Expand Up @@ -100,9 +107,13 @@ on the next write. Unknown provider keys round-trip verbatim. A malformed file f
with an error but resolves as empty on the read path, so one bad write does not break every later
invocation.

`auth set github-copilot` is refused. The agent normalizes every credential into an `api_key`
config field, and that provider's module reads only the environment, so a stored value would report
success and change nothing. This refusal is temporary and specific to that one provider.
`auth set github-copilot` and `auth set openai-chatgpt` are both refused, for different reasons.
The agent normalizes every credential into an `api_key` config field: github-copilot's module reads
only the environment and ignores it, so a stored value would report success and change nothing.
openai-chatgpt has no static key at all -- it authenticates via OAuth device-code and caches tokens
to `~/.amplifier/openai-chatgpt-oauth.json`, refreshed by the provider module itself. Both refusals
are enumerated in the same `_CONFIG_CREDENTIAL_UNSUPPORTED` gate; this is temporary and specific to
these two providers.

`auth clear` without `--force` exits 2.

Expand All @@ -114,7 +125,7 @@ success and change nothing. This refusal is temporary and specific to that one p
3. no further fallback: a bundle declaring neither is a hard error at boot
```

`provider.module` is closed to the five supported names. Any other value fails validation with
`provider.module` is closed to the six supported names. Any other value fails validation with
error code `config_invalid_provider_module`. `"auto"` is not a valid value.

There is no `--provider` flag and no environment-based provider auto-detection. See Non-goals.
Expand Down
6 changes: 3 additions & 3 deletions skills/amplifier-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ metadata:

`amplifier-agent` is an agent engine that other software runs on. Give it a prompt and it runs the full loop, with tools, sub-agents, skills, and MCP, then returns a result. Anything that can spawn a subprocess can use it; Python hosts can embed the engine library in-process instead.

Reach for it when the project needs an *agent* (a tool loop, file access, sub-agents, multi-turn state) rather than a single completion. You can also use it for plain LLM calls, with routing across five providers behind one interface.
Reach for it when the project needs an *agent* (a tool loop, file access, sub-agents, multi-turn state) rather than a single completion. You can also use it for plain LLM calls, with routing across six providers behind one interface.

**The engine runs one turn per invocation and exits.** Continuity across turns comes from a session id, not from a long-lived process. Every surface below is a different way of delivering a prompt to that same engine.

Expand Down Expand Up @@ -54,7 +54,7 @@ The installer needs `uv` and `curl` and will not bootstrap them silently; it tel

Install as **the same user that runs the host process**; a host spawning a subprocess inherits that user's `PATH`. `amplifier-agent doctor` is the check that the install actually works, so run it before writing any integration code.

Credentials are read from the environment, first match wins: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `AZURE_OPENAI_API_KEY` plus `AZURE_OPENAI_ENDPOINT`, `OLLAMA_HOST`. GitHub Copilot is environment-only (`COPILOT_AGENT_TOKEN`, `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN`). Or store one with `amplifier-agent auth set anthropic sk-ant-...`.
Credentials are read from the environment, first match wins: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `AZURE_OPENAI_API_KEY` plus `AZURE_OPENAI_ENDPOINT`, `OLLAMA_HOST`. GitHub Copilot is environment-only (`COPILOT_AGENT_TOKEN`, `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN`). ChatGPT (`openai-chatgpt`) has no credential env var at all: it authenticates via OAuth device-code, caching tokens to `~/.amplifier/openai-chatgpt-oauth.json`. Or store a static key with `amplifier-agent auth set anthropic sk-ant-...` (not supported for github-copilot or openai-chatgpt).

## Pick a surface

Expand Down Expand Up @@ -191,7 +191,7 @@ A per-instance config file looks like this:
| `config_unreadable`, `config_malformed_json` | The `--config` file could not be opened, or is not a JSON object | Check the path the host wrote, and that it serialized an object |
| `config_unknown_key` | Unrecognized **top-level** config key | The top level is closed: `approval`, `provider`, `providers`, `mcp`, `skills`, `debug`, `allowProtocolSkew` |
| `config_invalid_type` | A known key has the wrong type, or an unknown sub-key in a closed inner shape | `skills.*` and `debug.*` are closed and raise this rather than `config_unknown_key`, which is reserved for the top level and `providers.<id>` entries |
| `config_invalid_provider_module` | `provider.module` is not a known provider | One of `anthropic`, `openai`, `azure-openai`, `ollama`, `github-copilot`. `"auto"` is not valid |
| `config_invalid_provider_module` | `provider.module` is not a known provider | One of `anthropic`, `openai`, `azure-openai`, `ollama`, `github-copilot`, `openai-chatgpt`. `"auto"` is not valid |
| `protocol_version_mismatch` | Wrapper and engine protocol versions differ | Update the lagging side. `allowProtocolSkew` is an unblock, not a fix |
| `lifecycle_unsupported` | `submit()` called twice on one handle | New handle per turn, same `sessionId` with `resume` |
| `env_injection_rejected` | The wrapper refused the environment you asked it to inject | Check the key against the wrapper's allowlist and blocked-key list |
Expand Down
2 changes: 1 addition & 1 deletion src/amplifier_agent_cli/admin/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
#: agent-delivered credential from config first, then its env chain, then cached
#: OAuth. When that lands, DELETE this constant and its gate in ``auth_set``
#: outright. Do NOT grow it into a general provider-capability mechanism.
_CONFIG_CREDENTIAL_UNSUPPORTED: Final[frozenset[str]] = frozenset({"github-copilot"})
_CONFIG_CREDENTIAL_UNSUPPORTED: Final[frozenset[str]] = frozenset({"github-copilot", "openai-chatgpt"})


# ---------------------------------------------------------------------------
Expand Down
49 changes: 48 additions & 1 deletion src/amplifier_agent_cli/provider_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,14 @@ def _emit_legacy_env_var_notice(legacy_var: str, preferred_var: str) -> None:
#: ``models list --provider <name>``, or aggregate iteration in admin
#: commands) against the supported set. Kept in sync with
#: ``PROVIDER_CATALOG.keys()``.
KNOWN_PROVIDERS: Final[tuple[str, ...]] = ("anthropic", "openai", "azure-openai", "ollama", "github-copilot")
KNOWN_PROVIDERS: Final[tuple[str, ...]] = (
"anthropic",
"openai",
"azure-openai",
"ollama",
"github-copilot",
"openai-chatgpt",
)


#: Map provider short-name → bootstrap catalog row.
Expand Down Expand Up @@ -136,6 +143,10 @@ def _emit_legacy_env_var_notice(legacy_var: str, preferred_var: str) -> None:
"module": "provider-github-copilot",
"source": "git+https://github.com/microsoft/amplifier-module-provider-github-copilot@main",
},
"openai-chatgpt": {
"module": "provider-openai-chatgpt",
"source": "git+https://github.com/microsoft/amplifier-module-provider-openai-chatgpt@main",
},
}


Expand Down Expand Up @@ -375,6 +386,42 @@ def resolve_credential_detailed(provider_name: str) -> CredentialResolution:
fields={"host": _OLLAMA_DEFAULT_HOST},
)

if provider_name == "openai-chatgpt":
# ChatGPT subscription provider (provider-openai-chatgpt): authenticates
# via OAuth device-code, NOT an api key. There is no credential env var;
# the module caches its OAuth tokens to a file and refreshes them itself.
# We report "resolvable" iff that token cache exists and parses with a
# token present -- an honest signal of whether a prior device-code login
# happened -- without ever reading or emitting the token material. The
# module's own ``login_on_mount`` drives the interactive device-code flow
# at mount time when no cache exists. ``auth set`` is refused for this
# provider (see _CONFIG_CREDENTIAL_UNSUPPORTED in admin.auth) since there
# is no static key to store.
import json
from pathlib import Path

token_file = Path("~/.amplifier/openai-chatgpt-oauth.json").expanduser()
try:
data = json.loads(token_file.read_text())
has_token = isinstance(data, dict) and bool(data.get("access_token") or data.get("refresh_token"))
except (OSError, ValueError):
has_token = False
if has_token:
return CredentialResolution(
provider=provider_name,
resolved=True,
source="file",
env_var=None,
fields={},
)
return CredentialResolution(
provider=provider_name,
resolved=False,
source="none",
env_var=None,
fields={},
)

env_vars = PROVIDER_CREDENTIAL_VARS.get(provider_name)
if not env_vars:
return CredentialResolution(provider=provider_name, resolved=False, source="none", env_var=None, fields={})
Expand Down
2 changes: 2 additions & 0 deletions src/amplifier_agent_lib/bundle/bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ providers:
source: git+https://github.com/microsoft/amplifier-module-provider-ollama@main
- module: provider-github-copilot
source: git+https://github.com/microsoft/amplifier-module-provider-github-copilot@main
- module: provider-openai-chatgpt
source: git+https://github.com/microsoft/amplifier-module-provider-openai-chatgpt@main

session:
raw: true
Expand Down
Loading