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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,14 @@ Connect providers in OpenCode or run:
```sh
docker compose exec opencode opencode auth login github-copilot --server http://127.0.0.1:4096
docker compose exec opencode opencode auth login google --server http://127.0.0.1:4096
docker compose exec opencode opencode auth login anthropic --method key --server http://127.0.0.1:4096
docker compose exec opencode opencode auth login openai --server http://127.0.0.1:4096
```

- **GitHub Copilot:** complete device login with a subscribed account.
- **Gemini:** choose the API-key method or set `GOOGLE_GENERATIVE_AI_API_KEY`. Organization-backed Code Assist OAuth also needs `OPENCODE_GEMINI_PROJECT_ID`; see the [Gemini plugin's account requirements](https://github.com/jenslys/opencode-gemini-auth).
- **Anthropic:** enter a [Console API key](https://console.anthropic.com/settings/keys) or set `ANTHROPIC_API_KEY`. Claude Pro and Max subscriptions do not work outside Claude Code.
- **OpenAI:** choose the API-key method with a [platform key](https://platform.openai.com/api-keys) or set `OPENAI_API_KEY`.
- **Git access:** set `GH_TOKEN` or run `docker compose exec opencode gh auth login --hostname github.com --git-protocol https --web`. This is separate from Copilot login; stored credentials persist in `/data/.config/gh`.
- **WakaTime:** put your API key in `/data/.wakatime.cfg`, owned by `1000:1000` with mode `600`, using the [plugin's configuration format](https://github.com/angristan/opencode-wakatime#prerequisites).

Expand Down
2 changes: 2 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ services:
KARAKEEP_API_ADDR: ${KARAKEEP_API_ADDR:-}
KARAKEEP_API_KEY: ${KARAKEEP_API_KEY:-}
OPENCODE_PASSWORD: ${OPENCODE_PASSWORD:?Set OPENCODE_PASSWORD}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
volumes:
- data:/data
- type: bind
Expand Down
10 changes: 10 additions & 0 deletions opencode.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@
"action": "provider.use",
"resource": "github-copilot",
"effect": "allow"
},
{
"action": "provider.use",
"resource": "anthropic",
"effect": "allow"
},
{
"action": "provider.use",
"resource": "openai",
"effect": "allow"
}
]
}
6 changes: 5 additions & 1 deletion scripts/test-router-integration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ try {
assert.ok(google.methods.some(m => m.type === "oauth" && m.id === "gemini-cli"));
const copilot = (await request("/api/integration/github-copilot")).data;
assert.ok(copilot.methods.some(m => m.type === "oauth"));
for (const provider of ["anthropic", "openai"]) {
const { methods } = (await request(`/api/integration/${provider}`)).data;
assert.ok(methods.some(m => m.type === "key"), `${provider} accepts an API key: ${JSON.stringify(methods)}`);
}
const agents = (await request("/api/agent")).data;
const lead = agents.find(a => a.id === "build");
assert.ok(lead, "native build agent is available");
Expand Down Expand Up @@ -134,7 +138,7 @@ try {
});
// Without credentials, a dropped provider policy shows up as OpenCode's free models.
const models = (await request("/api/model")).data;
assert.ok(models.every(m => ["google", "github-copilot"].includes(m.providerID)), `policies hide other providers' models: ${models.map(m => m.providerID)}`);
assert.ok(models.every(m => ["google", "github-copilot", "anthropic", "openai"].includes(m.providerID)), `policies hide other providers' models: ${models.map(m => m.providerID)}`);
session = (await request("/api/session", { title: "V2 integration test", agent: "build",
location: { directory } })).data;
await request(`/api/session/${session.id}/command`, { name: "quota_announcements", text: "" });
Expand Down
2 changes: 2 additions & 0 deletions tests/routing.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ test("image-owned skills are discoverable", async () => {
test("existing providers, MCP integrations and quota are preserved", () => {
assert.deepEqual(Object.keys(config.mcp.servers).sort(), ["context7", "grafana", "karakeep", "lunchmoney", "notion", "robinhood"]);
assert.ok(config.plugins.includes("@slkiser/opencode-quota@5.0.2"));
const allowed = config.policies.filter(p => p.action === "provider.use" && p.effect === "allow").map(p => p.resource);
assert.deepEqual(allowed.sort(), ["anthropic", "github-copilot", "google", "openai"]);
assert.ok(!config.permissions?.some(rule => rule.action === "*" && rule.effect === "allow"));
assert.deepEqual(config.agents.build.permissions[0], { action: "*", resource: "*", effect: "allow" });
for (const name of ["general", "explore"])
Expand Down
Loading