Skip to content
Open
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
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ jobs:
workspace: "@cortexkit/opencode-antigravity-auth"
- package: "@cortexkit/pi-antigravity-auth"
workspace: "@cortexkit/pi-antigravity-auth"
- package: "@cortexkit/opencode-v2-antigravity-auth"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Adding @cortexkit/opencode-v2-antigravity-auth to the publish matrix breaks the release because its version is never synchronized. The workflow's Sync version step runs scripts/version-sync.mjs <tag-version>, whose packageJsonPaths array only covers packages/{core,opencode,pi}/package.json and omits packages/opencode-v2/package.json. So opencode-v2 always publishes its hardcoded 2.1.0, which diverges from the release tag and, on the second release, collides with the already-published version and fails the npm publish job (which blocks the downstream github-release). Add the opencode-v2 path to version-sync.mjs alongside this matrix entry.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release.yml, line 82:

<comment>Adding `@cortexkit/opencode-v2-antigravity-auth` to the publish matrix breaks the release because its version is never synchronized. The workflow's `Sync version` step runs `scripts/version-sync.mjs <tag-version>`, whose `packageJsonPaths` array only covers `packages/{core,opencode,pi}/package.json` and omits `packages/opencode-v2/package.json`. So opencode-v2 always publishes its hardcoded `2.1.0`, which diverges from the release tag and, on the second release, collides with the already-published version and fails the `npm publish` job (which blocks the downstream `github-release`). Add the opencode-v2 path to `version-sync.mjs` alongside this matrix entry.</comment>

<file context>
@@ -79,6 +79,8 @@ jobs:
             workspace: "@cortexkit/opencode-antigravity-auth"
           - package: "@cortexkit/pi-antigravity-auth"
             workspace: "@cortexkit/pi-antigravity-auth"
+          - package: "@cortexkit/opencode-v2-antigravity-auth"
+            workspace: "@cortexkit/opencode-v2-antigravity-auth"
     steps:
</file context>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b9136d9: scripts/version-sync.mjs now includes packages/opencode-v2/package.json in packageJsonPaths, so release tags bump the adapter version together with core/opencode/pi and the publish matrix cannot collide with a stale hardcoded version.

workspace: "@cortexkit/opencode-v2-antigravity-auth"
steps:
- uses: actions/checkout@v5
with:
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

Google Antigravity OAuth for coding agents. Authenticate with your Google account and access Antigravity quota for Gemini, Claude, and GPT-OSS models from OpenCode, the Pi coding agent, or the standalone CLI.

This monorepo ships three packages:
This monorepo ships four packages:
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

| Package | Host | Role |
| --- | --- | --- |
| [`@cortexkit/opencode-antigravity-auth`](packages/opencode) | OpenCode 1.x server | Intercepts `fetch()`, runs the account pool + quota manager, drives slash commands, and exposes a TUI sidebar through a loopback RPC. |
| [`@cortexkit/pi-antigravity-auth`](packages/pi) | Pi coding agent | Registers a custom provider with OAuth login + Gemini streaming. |
| [`@cortexkit/opencode-v2-antigravity-auth`](packages/opencode-v2) | OpenCode 2.x server | Registers the Antigravity models through the native plugin API: a loopback bridge for the account pool and transport, an OAuth method that appends accounts, and a document tool for PDF input. |
| [`@cortexkit/antigravity-auth-core`](packages/core) | Any harness | Harness-agnostic core: OAuth PKCE, raw HTTP/1.1 transport, device fingerprint, request transforms, account pool, quota manager, durable storage. Both host packages depend on it. |

## Risk and terms-of-service warning
Expand Down Expand Up @@ -404,14 +405,14 @@ The script enforces semver, refuses to run on a dirty tree, and refuses to run o
The `Release` workflow (`.github/workflows/release.yml`) runs on tag push (or `workflow_dispatch`) and:

1. **Test** job — checkout the tag ref → install Bun 1.3 + Node 24 → `bun install --frozen-lockfile` → `bun run typecheck` → `bun run build` → `bun run --cwd packages/opencode smoke:tui` → `bun test` → `bun run test:e2e` → `bun run format:check` → `bun run lint`.
2. **Publish** job — matrixed across the three packages (`@cortexkit/antigravity-auth-core`, `@cortexkit/opencode-antigravity-auth`, `@cortexkit/pi-antigravity-auth`) with `max-parallel: 1` so each package picks up its own OIDC token; `npm Trusted Publishing` (`npm publish --workspace … --access public --provenance`) handles the upload. The job skips a package if its npm version is already published.
2. **Publish** job — matrixed across the four packages (`@cortexkit/antigravity-auth-core`, `@cortexkit/opencode-antigravity-auth`, `@cortexkit/pi-antigravity-auth`, `@cortexkit/opencode-v2-antigravity-auth`) with `max-parallel: 1` so each package picks up its own OIDC token; `npm Trusted Publishing` (`npm publish --workspace … --access public --provenance`) handles the upload. The job skips a package if its npm version is already published.
3. **GitHub Release** job — `softprops/action-gh-release@v2` creates the GitHub release with `generate_release_notes: true`.

CI (`.github/workflows/ci.yml`) reuses the same typecheck → build → smoke → test → e2e → format → lint chain on every push to `main` and on every pull request. The `issue-triage.yml` workflow handles GitHub-issue routing.

## Architecture and structure links

- [ARCHITECTURE.md](ARCHITECTURE.md) — system-of-record architecture doc across all three packages.
- [ARCHITECTURE.md](ARCHITECTURE.md) — system-of-record architecture doc across all four packages.
- [STRUCTURE.md](STRUCTURE.md) — file-system map of the monorepo.
- [packages/opencode/ARCHITECTURE.md](packages/opencode/ARCHITECTURE.md) — OpenCode plugin architecture in detail.
- [packages/opencode/STRUCTURE.md](packages/opencode/STRUCTURE.md) — OpenCode package file-system map.
Expand Down
1 change: 1 addition & 0 deletions biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"packages/*/scripts/**",
"scripts/**",
"test/**",
"packages/*/test/**",
"*.json",
"packages/*/*.json",
".github/**/*.yml"
Expand Down
15 changes: 12 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
],
"scripts": {
"build": "bun run --cwd packages/core build && bun run --cwd packages/opencode build && bun run --cwd packages/pi build",
"typecheck": "bun run --cwd packages/core build && bun run --cwd packages/opencode typecheck && bun run --cwd packages/pi typecheck && tsc -p tsconfig.scripts.json",
"test": "bun run --cwd packages/core build && bun test --isolate packages/core/src packages/opencode/src packages/pi/src test/",
"typecheck": "bun run --cwd packages/core build && bun run --cwd packages/opencode typecheck && bun run --cwd packages/pi typecheck && tsc -p tsconfig.scripts.json && node --check packages/opencode-v2/src/plugin.mjs && node --check packages/opencode-v2/src/oauth-callback.mjs",
"test": "bun run --cwd packages/core build && bun test --isolate packages/core/src packages/opencode/src packages/pi/src test/ packages/opencode-v2/test/",
"test:e2e": "bun test --isolate ./packages/e2e-tests/src/plugin-flow.e2e.test.ts ./packages/e2e-tests/src/cli-flow.e2e.test.ts ./packages/e2e-tests/src/rpc-tui-flow.e2e.test.ts ./packages/e2e-tests/src/fetch-guard.test.ts ./packages/e2e-tests/src/mock-antigravity-server.test.ts",
"test:e2e:models": "bun run --cwd packages/opencode test:e2e:models",
"test:e2e:regression": "bun run --cwd packages/opencode test:e2e:regression",
Expand Down
21 changes: 21 additions & 0 deletions packages/opencode-v2/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
174 changes: 174 additions & 0 deletions packages/opencode-v2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# opencode-v2-antigravity

Google Antigravity provider for **OpenCode 2.x**, built on
[`@cortexkit/antigravity-auth-core`](https://www.npmjs.com/package/@cortexkit/antigravity-auth-core).

`@cortexkit/opencode-antigravity-auth` targets the OpenCode 1.x host
(`engines.opencode: ">=1.17.13 <2"`): it patches `fetch()` and registers a TUI sidebar.
OpenCode 2.x replaced that surface with a typed plugin API (`session.hook`,
`integration.transform`, `tool.transform`, native provider packages), so the 1.x plugin cannot
load there. This package is that missing host adapter — OAuth, transport, account pool, quota
bookkeeping and the model registry all stay in the shared core.

> **Terms-of-service warning.** This calls Antigravity's non-public internal API. It is not
> endorsed by Google and may violate Google's Terms of Service; accounts have reportedly been
> suspended for similar use. Use at your own risk and never with an important account.

## Design

```
OpenCode 2.x this plugin Antigravity
──────────── ─────────── ───────────
native @opencode-ai/ai/providers/google
builds Gemini request ──▶ session.hook("http.request")
rewrites the URL to a 127.0.0.1 loopback
loopback HTTP server
· picks an account (hybrid strategy)
· refreshes the OAuth token
· ensureProjectContext()
· agent envelope + labels/sessionId
· fetchWithAgyCliTransport() ──▶ daily-cloudcode-pa
(fallback cloudcode-pa)
parses Gemini SSE ◀─────── unwrapped, schema-normalised SSE
```

Keeping the native `@opencode-ai/ai/providers/google` package as the codec means image, PDF and
tool-call handling comes from the host instead of a hand-written adapter.

Why a loopback server instead of returning a `Response` from the hook: OpenCode 2.x sends
whatever `event.request` the hook leaves behind through its own HTTP client, and the
`http.response` hook only runs after that request succeeded. A loopback endpoint keeps the
core's raw HTTP/1.1 transport (agy header order, proxy support) while the host still sees a
plain SSE response it can stream and cancel.

## Install

Install the adapter package itself (the shared core is pulled in automatically):

```bash
# once the package is published:
npm install @cortexkit/opencode-v2-antigravity-auth
# or from this repository (Bun workspace):
bun install
```

The plugin entry is `src/plugin.mjs` inside the package. In `opencode.json`, point
`plugins[].package` at that file — for an npm install use
`node_modules/@cortexkit/opencode-v2-antigravity-auth/src/plugin.mjs` relative to the project,
for a checkout use the absolute path
`/path/to/antigravity-auth/packages/opencode-v2/src/plugin.mjs`. The example below uses a
checkout path.

Register the plugin and the models in `opencode.json` (full snippet in
[`example/opencode.json`](example/opencode.json)):

```jsonc
{
"plugins": [
{
"package": "/absolute/path/to/antigravity-auth/packages/opencode-v2/src/plugin.mjs",
"options": {
// Optional: restrict which directories antigravity_read_document may read.
// Default: anything under the user's home directory (credential/secret
// paths are always blocked).
"readDocumentRoots": ["/absolute/path/to/project/docs"]
}
}
],
"providers": {
"google": {
"models": {
"gemini-3.7-flash": {
"name": "Gemini 3.7 Flash",
"modelID": "gemini-3.7-flash",
"package": "@opencode-ai/ai/providers/google",
"capabilities": { "tools": true, "input": ["text", "image", "pdf"], "output": ["text"] },
"limit": { "context": 1048576, "output": 65536 },
"variants": [{ "id": "low" }, { "id": "medium" }, { "id": "high" }]
}
}
}
}
}
```

## Accounts

- Pool file: `antigravity-accounts.json` in the OpenCode config dir
(`$OPENCODE_CONFIG_DIR`, `$XDG_CONFIG_HOME/opencode`, `%APPDATA%\opencode`, or
`~/.config/opencode`); override with `ANTIGRAVITY_ACCOUNTS_FILE`. Storage schema v4 with the
core's fenced file lock, so the pool is shared with the 1.x plugin and the standalone CLI.
- Add an account: connect the `google` integration and pick
**"Google Antigravity (add account)"**. Each login appends to the pool; existing accounts are
preserved. The callback listens on `127.0.0.1:51121/oauth-callback`.
- Disable an account with `"enabled": false`.
- Selection uses the core `hybrid` strategy. On `429`/`403` the account is cooled down and the
next one is tried, `401` forces a token refresh, and a bare `STOP` (empty candidates) is
retried up to three times.

## Models

| Selector | Variants | Wire model |
| --- | --- | --- |
| `google/gemini-3.7-flash` | low, medium, high | `gemini-3.7-flash-{tier}` |
| `google/gemini-3.6-flash` | low, medium, high | `gemini-3.6-flash-{tier}` |
| `google/gemini-3.5-flash` | low, medium, high | `gemini-3.5-flash-extra-low` / `gemini-3.5-flash-low` / `gemini-3-flash-agent` |
| `google/gemini-3.1-pro` | low, high | `gemini-3.1-pro-low` / `gemini-pro-agent` |
| `google/gemini-3.1-flash-image` | — | `gemini-3.1-flash-image` |
| `google/claude-sonnet-4-6-thinking` | — | `claude-sonnet-4-6` |
| `google/claude-opus-4-6-thinking` | — | `claude-opus-4-6-thinking` |
| `google/gpt-oss-120b-medium` | — | `gpt-oss-120b-medium` |

Model ids and tiers come from `resolveModelForHeaderStyle()`, so the registry stays the single
source of truth.

## Host quirks this plugin works around

1. **GPT-OSS tool schemas** — the AGY GPT bridge re-encodes protobuf numeric constraints as
strings, so `minLength: 1` fails OpenAI JSON-Schema validation with `400 INVALID_ARGUMENT`.
Fixed by calling `normalizeGeminiTools(request, { moveNumericConstraintsToDescription: true })`
for `gpt-*` wire models.
2. **Strict native event schema** — GPT-OSS opens a turn with `content` and no `parts`, and
Claude sometimes uses role `assistant`. Both are rejected by the native Gemini event schema
(`Invalid google/gemini stream event`), so every frame is normalised before being forwarded.
3. **Response encoding** — the core transport already inflates gzip, so upstream
`content-encoding` headers must not be copied onto the loopback response.
4. **PDF attachments** — the OpenCode 2.x CLI drops PDF attachments before the provider sees
them (the request arrives without any `inlineData`). The plugin therefore also registers an
`antigravity_read_document` tool that loads the file itself:
`antigravity_read_document({ path, question?, model? })` for `.pdf`, `.png`, `.jpg`, `.webp`,
`.gif`, `.heic`. Images attached in chat work without the tool.
**Security note:** the tool reads local files and sends them to the Antigravity server —
an untrusted PDF/image is a prompt-injection vector that could instruct the model to read
sensitive files. Paths are therefore checked: well-known credential/secret locations
(`~/.ssh`, `~/.config`, `~/.local`, `AppData`, `.env`, `*.key`, `*.pem`, `*.p12`, `*.pfx`,
`id_rsa`, `credentials`, `auth.json`, `antigravity-accounts.json`, …) are always refused,
and by default only files under the user's home directory are readable. To narrow the
readable root further, set the `readDocumentRoots` plugin option (array of absolute
directories). Prefer attaching documents in chat where the host preserves them.
5. **Image output** — the native parser renders text and tool calls only, so generated images
are written to `<data dir>/antigravity-images/` and announced as text.

## Logging and privacy

`<state dir>/antigravity-v2.log` records routing, `#<account index>`, upstream status codes,
rotation and saved image paths. No prompts, tokens, e-mail addresses or refresh tokens are
written. Credentials live only in the pool file owned by the core.

## Verified

Windows 11, Node 24, OpenCode `0.0.0-beta-17595`, two-account pool:

- all eight selectors above answered a live prompt, including every reasoning tier;
- tool calling works end to end (the model called `read` and returned a directory listing);
- PNG attachment recognised (a red square → "Red");
- PDF read through `antigravity_read_document` (exact embedded string returned);
- image generation produced two JPEG files on disk;
- forced failover: disabling account `#0` routed the next request to account `#1`.

## License

MIT.
Loading