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
7 changes: 7 additions & 0 deletions .amplifier/digital-twin-universe/profiles/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ passthrough:
# the whole auth story: no credential files, no device flow, no mounts.
# Set it host-side with `export GITHUB_TOKEN=$(gh auth token)` before launching.
- { name: github_token, key_env: GITHUB_TOKEN }
# Only the gemini suite needs this. GOOGLE_API_KEY is the canonical variable: the
# Google GenAI SDK also accepts GEMINI_API_KEY, but GOOGLE_API_KEY takes precedence
# and is the sole entry in PROVIDER_CREDENTIAL_VARS, so it is what `providers list`
# and `models list` consult. Unset is safe -- DTU's passthrough writes the export
# under a bare `if value:` guard, so an absent value produces no export and no error,
# and the gemini suite skips itself rather than failing.
- { name: google, key_env: GOOGLE_API_KEY }

url_rewrites:
# amplifier-agent is git-installed (uv tool install git+...), so we rewrite the
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **Gemini provider.** `provider.module: "gemini"` is now a valid host-config value,
backed by `amplifier-module-provider-gemini`. It talks to Google's Gemini API, with
large context windows and thinking/reasoning support. Unlike `openai-chatgpt` and
`chat-completions` (added in 0.13.0), it is a normal key-based provider like
`anthropic` and `openai`: credentials resolve from `GOOGLE_API_KEY` (the module's own
env read also accepts `GEMINI_API_KEY`, `GOOGLE_API_KEY` taking precedence when both
are set), and `auth set gemini` is accepted and stores the key like any other keyed
provider. Default model is `gemini-2.5-flash`.

## [0.13.0] — 2026-08-18

### 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:

- Seven providers behind one interface: Anthropic, OpenAI, Azure OpenAI, Ollama, GitHub Copilot, ChatGPT (a Plus/Pro/Team subscription via OAuth device-code, no API key), and Chat Completions (any OpenAI Chat Completions-compatible endpoint, e.g. llama.cpp, vLLM, LM Studio), with credentials read from the environment or a cached OAuth session
- Eight providers behind one interface: Anthropic, OpenAI, Azure OpenAI, Ollama, GitHub Copilot, ChatGPT (a Plus/Pro/Team subscription via OAuth device-code, no API key), Chat Completions (any OpenAI Chat Completions-compatible endpoint, e.g. llama.cpp, vLLM, LM Studio), and Gemini (Google's Gemini API, large context windows plus thinking/reasoning support), 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
2 changes: 2 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Provider is auto-detected from environment variables in this precedence:
2. `OPENAI_API_KEY`
3. `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_ENDPOINT`
4. `OLLAMA_HOST` (defaults to `http://localhost:11434`)
5. `GOOGLE_API_KEY` (`GEMINI_API_KEY` is also accepted by the provider module, `GOOGLE_API_KEY` takes precedence)

`github-copilot`, `openai-chatgpt`, and `chat-completions` are excluded from this auto-detect chain
-- none of them resolves from a single API-key environment variable. `github-copilot` reads its own
Expand Down Expand Up @@ -52,6 +53,7 @@ For "set once, works everywhere" instead of editing shell rc files, the `auth` s
amplifier-agent auth set anthropic sk-ant-...
amplifier-agent auth set openai sk-...
amplifier-agent auth set azure-openai sk-... --endpoint https://...
amplifier-agent auth set gemini AIza...
amplifier-agent auth list # configured providers, api keys masked
amplifier-agent auth status # diagnose env-vs-file precedence per provider
amplifier-agent auth remove openai # delete a single entry
Expand Down
13 changes: 10 additions & 3 deletions docs/E2E_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,16 @@ required by any test that runs a real model and by the HTTP server startup.
`GITHUB_TOKEN` must be set for the `github_copilot` suite (only that suite; everything else
runs without it). Set it with `export GITHUB_TOKEN=$(gh auth token)` and re-provision. The
value is snapshotted into the container at launch, so exporting it after a DTU is already
running has no effect. `dtu_manager.provision()` warns when either variable is missing,
because DTU's passthrough silently skips an unset value and the failure would otherwise
surface much later as an opaque provider auth error.
running has no effect. `dtu_manager.provision()` warns when any of these variables is
missing, because DTU's passthrough silently skips an unset value and the failure would
otherwise surface much later as an opaque provider auth error.

`GOOGLE_API_KEY` is optional and only the `gemini` suite uses it. Without it that suite
skips itself rather than failing, so a full run stays green for anyone who has no Google
credential. Set it to run the suite, and re-provision afterwards for the same
snapshot-at-launch reason. `GOOGLE_API_KEY` is the canonical variable even though the
Google GenAI SDK also accepts `GEMINI_API_KEY`: it takes precedence, and it is the one
`providers list` and `models list` consult.

## Running

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 seven 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 eight 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`, `provider-openai-chatgpt`, `provider-chat-completions`
- **Providers:** `provider-anthropic`, `provider-openai`, `provider-azure-openai`, `provider-ollama`, `provider-github-copilot`, `provider-openai-chatgpt`, `provider-chat-completions`, `provider-gemini`
- **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 · chatgpt\nchat-completions"];
providers [label="LLM providers\nanthropic · openai · azure\nollama · copilot · chatgpt\nchat-completions · gemini"];
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 @@ -31,7 +31,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-openai-chatgpt,
provider-chat-completions
provider-chat-completions, provider-gemini

session.orchestrator: loop-streaming extended_thinking: true
session.context: context-simple max_tokens 300000, auto_compact
Expand Down
6 changes: 3 additions & 3 deletions docs/spec/host-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ approval.patterns must be a list of strings

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

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

`module` defaults to the entry's own id when omitted and must be one of the six valid module names.
`module` defaults to the entry's own id when omitted and must be one of the eight 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 Expand Up @@ -216,7 +216,7 @@ config_invalid_type A typed field has the wrong shape: approval.patte
debug not a dict, unknown sub-key under debug.*, or
debug.rawLlmPayloads not a bool; providers not an object, bad entry
shape, or non-dict entry config.
config_invalid_provider_module provider.module outside the 5 valid names, or providers.<id>.module
config_invalid_provider_module provider.module outside the 8 valid names, or providers.<id>.module
outside them.
config_no_matching_module host_config declares a non-empty `skills:` block but the bundle has
no skills tool module mounted. An empty skills block plus a missing
Expand Down
15 changes: 12 additions & 3 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

Seven providers are supported, and only seven. The provider name is the value used in configuration,
Eight providers are supported, and only eight. The provider name is the value used in configuration,
in `auth` subcommands, and in `models list --provider`.

```
Expand All @@ -20,10 +20,11 @@ ollama provider-ollama
github-copilot provider-github-copilot
openai-chatgpt provider-openai-chatgpt
chat-completions provider-chat-completions
gemini provider-gemini
```

Each module is installed from `git+https://github.com/microsoft/amplifier-module-<module>@main`.
All seven are declared by the shipped bundle (`bundle.md`'s top-level `providers:` stub list) as
All eight are declared by the shipped bundle (`bundle.md`'s top-level `providers:` stub list) 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 Down Expand Up @@ -52,6 +53,7 @@ ollama OLLAMA_HOST, then OLLAMA_BASE_URL
github-copilot GITHUB_TOKEN
openai-chatgpt (none -- OAuth device-code)
chat-completions CHAT_COMPLETIONS_BASE_URL, plus optional CHAT_COMPLETIONS_API_KEY
gemini GOOGLE_API_KEY
```

`AZURE_OPENAI_KEY` is the only deprecated alias. Consulting it emits a one-time warning on stderr.
Expand Down Expand Up @@ -80,6 +82,13 @@ chat-completions -- with no `CHAT_COMPLETIONS_BASE_URL` in the environment it re
unconditionally to `source == "none"`, with no file fallback and no usable default to fall back
to (unlike ollama's built-in localhost).

gemini lists only `GOOGLE_API_KEY` here. The Google GenAI SDK also accepts `GEMINI_API_KEY`
(`GOOGLE_API_KEY` takes precedence when both are set), and the provider module's own env read
honours that; listing `GEMINI_API_KEY` in this table would mark it deprecated, which it is not.
Otherwise gemini follows the generic env-then-file chain like anthropic and openai: it is a normal
key-based provider, `auth set gemini` is accepted, and it is not excluded from the credential model
the way github-copilot, openai-chatgpt, and chat-completions are.

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 @@ -144,7 +153,7 @@ those instead of relying on `auth set` for this provider.
3. no further fallback: a bundle declaring neither is a hard error at boot
```

`provider.module` is closed to the seven supported names. Any other value fails validation with
`provider.module` is closed to the eight 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
4 changes: 2 additions & 2 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 seven 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 eight 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 @@ -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`, `openai-chatgpt`, `chat-completions`. `"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`, `chat-completions`, `gemini`. `"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
12 changes: 12 additions & 0 deletions src/amplifier_agent_cli/provider_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def _emit_legacy_env_var_notice(legacy_var: str, preferred_var: str) -> None:
"github-copilot",
"openai-chatgpt",
"chat-completions",
"gemini",
)


Expand Down Expand Up @@ -152,6 +153,10 @@ def _emit_legacy_env_var_notice(legacy_var: str, preferred_var: str) -> None:
"module": "provider-chat-completions",
"source": "git+https://github.com/microsoft/amplifier-module-provider-chat-completions@main",
},
"gemini": {
"module": "provider-gemini",
"source": "git+https://github.com/microsoft/amplifier-module-provider-gemini@main",
},
}


Expand Down Expand Up @@ -185,6 +190,13 @@ def _emit_legacy_env_var_notice(legacy_var: str, preferred_var: str) -> None:
# listing them would produce a spurious deprecation warning. amplifier-agent only
# needs one var to answer "is this provider configured".
"github-copilot": ("GITHUB_TOKEN",),
# Google GenAI SDK accepts BOTH GOOGLE_API_KEY and GEMINI_API_KEY as
# first-class (GOOGLE_API_KEY takes precedence). Only the primary is listed
# here: entries past index 0 are treated as deprecated aliases and emit a
# spurious stderr deprecation notice, which GEMINI_API_KEY is not. A
# GEMINI_API_KEY-only user is still served by the module's own env read at
# mount; amplifier-agent only needs one var to answer "is this configured".
"gemini": ("GOOGLE_API_KEY",),
}

#: Ollama's own env var chain includes a second, non-legacy alias
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 @@ -72,6 +72,8 @@ providers:
source: git+https://github.com/microsoft/amplifier-module-provider-openai-chatgpt@main
- module: provider-chat-completions
source: git+https://github.com/microsoft/amplifier-module-provider-chat-completions@main
- module: provider-gemini
source: git+https://github.com/microsoft/amplifier-module-provider-gemini@main

session:
raw: true
Expand Down
2 changes: 1 addition & 1 deletion src/amplifier_agent_lib/config/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

_VALID_TOP_LEVEL_KEYS = frozenset({"mcp", "approval", "provider", "providers", "allowProtocolSkew", "skills", "debug"})
_VALID_PROVIDER_MODULES = frozenset(
{"anthropic", "openai", "azure-openai", "ollama", "github-copilot", "openai-chatgpt", "chat-completions"}
{"anthropic", "openai", "azure-openai", "ollama", "github-copilot", "openai-chatgpt", "chat-completions", "gemini"}
)
# G3: explicit set of host-supplied approval modes. ``CliApprovalSystem`` accepts
# exactly these three strings; any other value must be rejected at parse time
Expand Down
14 changes: 8 additions & 6 deletions tests/e2e/framework/dtu_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,20 @@ def _check_passthrough_env() -> None:

Deliberately a warning, not a hard failure: these are per-suite requirements, and
a missing GITHUB_TOKEN should not block someone running the skills or modes suites.
The github_copilot suite enforces its own requirement directly (and inside the
container, which is what actually matters) via ``test_ghcp_token_reaches_dtu``.
Each suite enforces its own requirement directly, and inside the container, which
is what actually matters -- github_copilot fails loud via
``test_ghcp_token_reaches_dtu``, gemini skips itself via its ``gemini_key`` fixture.
"""
required = (
("ANTHROPIC_API_KEY", "most suites"),
("GITHUB_TOKEN", "the github_copilot suite"),
("ANTHROPIC_API_KEY", "most suites will fail"),
("GITHUB_TOKEN", "the github_copilot suite will fail"),
("GOOGLE_API_KEY", "the gemini suite will skip"),
)
for var, suite in required:
for var, consequence in required:
if not os.environ.get(var):
print(
f"[dtu_manager] warning: {var} is not set on this process, so it will NOT "
f"be exported inside the DTU; {suite} will fail."
f"be exported inside the DTU; {consequence}."
)


Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/suites/gemini/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""``gemini`` provider e2e suite."""

from __future__ import annotations
Loading