mirror of
https://github.com/NoeFabris/opencode-antigravity-auth.git
synced 2026-05-13 15:46:05 +00:00
better repo config
This commit is contained in:
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug or issue with the plugin
|
||||
title: '[BUG] '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Bug Description**
|
||||
A clear and concise description of the bug.
|
||||
|
||||
**Steps to Reproduce**
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected Behavior**
|
||||
What should happen.
|
||||
|
||||
**Actual Behavior**
|
||||
What actually happens.
|
||||
|
||||
**Environment**
|
||||
- opencode version:
|
||||
- Plugin version:
|
||||
- Operating System:
|
||||
- Node.js version:
|
||||
|
||||
**Logs**
|
||||
If applicable, attach logs (enable verbose logging with `export OPENCODE_ANTIGRAVITY_DEBUG=1`). Logs are saved in the current directory as `antigravity-debug-<timestamp>.log`.
|
||||
|
||||
**Compliance Checklist**
|
||||
Please confirm:
|
||||
- [ ] I'm using this plugin for personal development only
|
||||
- [ ] I have an active Google Cloud project with Antigravity enabled
|
||||
- [ ] This issue is not related to attempting commercial use or TOS violations
|
||||
- [ ] I've reviewed the README troubleshooting section
|
||||
|
||||
**Additional Context**
|
||||
Add any other relevant information.
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Google Cloud Support
|
||||
url: https://cloud.google.com/support
|
||||
about: For Google Cloud account or billing issues, contact Google Cloud directly
|
||||
- name: Google Terms of Service
|
||||
url: https://policies.google.com/terms
|
||||
about: Review Google's Terms of Service for compliance questions
|
||||
- name: Discussions
|
||||
url: https://github.com/NoeFabris/opencode-antigravity-auth/discussions
|
||||
about: Ask questions or discuss the plugin with the community
|
||||
28
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature or enhancement
|
||||
title: '[FEATURE] '
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Feature Description**
|
||||
A clear description of the feature you'd like to see.
|
||||
|
||||
**Use Case**
|
||||
Explain how this feature would be used and what problem it solves.
|
||||
|
||||
**Proposed Implementation**
|
||||
If you have ideas about how this could be implemented, share them here.
|
||||
|
||||
**Compliance Consideration**
|
||||
Please confirm:
|
||||
- [ ] This feature is for personal development use
|
||||
- [ ] This feature does not violate or circumvent Google's Terms of Service
|
||||
- [ ] This feature is not intended for commercial resale or multi-user access
|
||||
|
||||
**Alternatives Considered**
|
||||
Have you considered any alternative solutions or workarounds?
|
||||
|
||||
**Additional Context**
|
||||
Add any other context, screenshots, or examples.
|
||||
34
.github/workflows/ci.yml
vendored
Normal file
34
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test on Node.js
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run type check
|
||||
run: npm run typecheck
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
76
AGENTS.MD
Normal file
76
AGENTS.MD
Normal file
@@ -0,0 +1,76 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides coding guidance for AI agents when working with code in this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
This is an **opencode plugin** that enables OAuth authentication with Google's Antigravity IDE backend. It allows users to access models like `gemini-3-pro-high` and `claude-sonnet-4-5` using their Google credentials.
|
||||
|
||||
## Build & Test Commands
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build (compiles TypeScript)
|
||||
npm run build
|
||||
|
||||
# Type checking only
|
||||
npm run typecheck
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
```
|
||||
|
||||
## Code Architecture
|
||||
|
||||
### Plugin Flow (src/plugin.ts)
|
||||
|
||||
The main entry point `createAntigravityPlugin` orchestrates a request interception flow:
|
||||
|
||||
1. **Auth Validation**: Checks if the request is for the Antigravity provider and if OAuth is used.
|
||||
2. **Token Refresh**: Checks for expired access tokens and refreshes them using `refreshAccessToken`.
|
||||
3. **Project Context**: Resolves the effective Google Cloud project ID.
|
||||
4. **Endpoint Fallback**: Tries multiple Antigravity endpoints in sequence (`daily` → `autopush` → `prod`) if requests fail with specific error codes (403, 404, 429, 5xx).
|
||||
5. **Response Transformation**: Converts the Antigravity response format to what opencode expects.
|
||||
|
||||
### Module Organization
|
||||
|
||||
**Core Plugin** (`src/plugin.ts`)
|
||||
- Plugin definition and request interception logic.
|
||||
- Implements endpoint fallback strategy.
|
||||
|
||||
**Authentication** (`src/antigravity/oauth.ts`, `src/plugin/auth.ts`)
|
||||
- Handles OAuth flow, token exchange, and token validation.
|
||||
- `src/plugin/server.ts`: Local HTTP server for OAuth callback.
|
||||
|
||||
**Request Handling** (`src/plugin/request.ts`)
|
||||
- `prepareAntigravityRequest`: Prepares headers and body for Antigravity API.
|
||||
- `transformAntigravityResponse`: Handles response transformation.
|
||||
|
||||
**Configuration** (`src/constants.ts`)
|
||||
- Contains constants like provider IDs, redirect URIs, and endpoint URLs.
|
||||
|
||||
## Key Design Patterns
|
||||
|
||||
**1. Endpoint Fallback**:
|
||||
- The plugin robustly handles service availability by iterating through a list of fallback endpoints defined in `ANTIGRAVITY_ENDPOINT_FALLBACKS`.
|
||||
|
||||
**2. Automatic Token Refresh**:
|
||||
- Access tokens are automatically checked for expiration and refreshed before requests are sent.
|
||||
|
||||
**3. Debug Logging**:
|
||||
- Extensive debug logging is available via `startAntigravityDebugRequest` when enabled.
|
||||
|
||||
## TypeScript Configuration
|
||||
|
||||
- Target: `ESNext`
|
||||
- Module: `Preserve`
|
||||
- Module Resolution: `bundler`
|
||||
- Strict mode enabled.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@openauthjs/openauth`: Handles OAuth PKCE implementation.
|
||||
- `vitest`: Testing framework.
|
||||
- `typescript`: Peer dependency.
|
||||
63
bun.lock
63
bun.lock
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "opencode-antigravity-auth",
|
||||
"dependencies": {
|
||||
"@openauthjs/openauth": "^0.4.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opencode-ai/plugin": "^0.15.30",
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
|
||||
|
||||
"@opencode-ai/plugin": ["@opencode-ai/plugin@0.15.30", "", { "dependencies": { "@opencode-ai/sdk": "0.15.30", "zod": "4.1.8" } }, "sha512-m12MGUOTqhaNmt7Xyhs6IH1t8Ki2brvWi6Ho6m32D/827lsJp5fr6j9STGNGaRkTpaxKPNgMW5/xVrWIBenAfw=="],
|
||||
|
||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@0.15.30", "", {}, "sha512-Fu4iBoB+ksd9CFFmjiCeEW+x03wwUO3r8/cRlwSW5hVfPyMkY0mCkXFyTeB6n9g5IJ1m/ySpCUM0qzs4RBjyEA=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
"@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
|
||||
|
||||
"@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="],
|
||||
|
||||
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
|
||||
|
||||
"@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
|
||||
|
||||
"@types/node": ["@types/node@24.9.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||
|
||||
"arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="],
|
||||
|
||||
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"hono": ["hono@4.10.4", "", {}, "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg=="],
|
||||
|
||||
"jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
||||
|
||||
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
||||
}
|
||||
}
|
||||
1623
package-lock.json
generated
1623
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
58
package.json
58
package.json
@@ -1,23 +1,57 @@
|
||||
{
|
||||
"name": "opencode-antigravity-auth",
|
||||
"module": "index.ts",
|
||||
"version": "1.0.2",
|
||||
"author": "noefabris",
|
||||
"repository": "https://github.com/NoeFabris/opencode-antigravity-auth",
|
||||
"files": [
|
||||
"index.ts",
|
||||
"src"
|
||||
],
|
||||
"license": "MIT",
|
||||
"description": "Google Antigravity IDE OAuth auth plugin for Opencode - access Gemini 3 Pro and Claude 4.5 using Google credentials",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@opencode-ai/plugin": "^0.15.30",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^24.10.1"
|
||||
"license": "MIT",
|
||||
"author": "noefabris",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/NoeFabris/opencode-antigravity-auth.git"
|
||||
},
|
||||
"homepage": "https://github.com/NoeFabris/opencode-antigravity-auth#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/NoeFabris/opencode-antigravity-auth/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"opencode",
|
||||
"google",
|
||||
"antigravity",
|
||||
"gemini",
|
||||
"oauth",
|
||||
"plugin",
|
||||
"auth",
|
||||
"claude",
|
||||
"ide"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opencode-ai/plugin": "^0.15.30",
|
||||
"@types/node": "^24.10.1",
|
||||
"@vitest/ui": "^3.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vitest": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openauthjs/openauth": "^0.4.3"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ interface AntigravityUserTier {
|
||||
}
|
||||
|
||||
interface LoadCodeAssistPayload {
|
||||
cloudaicompanionProject?: string;
|
||||
cloudaicompanionProject?: string | { id?: string };
|
||||
currentTier?: {
|
||||
id?: string;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ANTIGRAVITY_PROVIDER_ID } from "../constants";
|
||||
import { refreshAccessToken } from "./token";
|
||||
@@ -14,21 +14,21 @@ const baseAuth: OAuthAuthDetails = {
|
||||
function createClient() {
|
||||
return {
|
||||
auth: {
|
||||
set: mock(async () => {}),
|
||||
set: vi.fn(async () => {}),
|
||||
},
|
||||
} as PluginClient & {
|
||||
auth: { set: ReturnType<typeof mock<(input: any) => Promise<void>>> };
|
||||
auth: { set: ReturnType<typeof vi.fn> };
|
||||
};
|
||||
}
|
||||
|
||||
describe("refreshAccessToken", () => {
|
||||
beforeEach(() => {
|
||||
mock.restore();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("updates the caller but skips persisting when refresh token is unchanged", async () => {
|
||||
it("updates the caller and persists when refresh token is unchanged", async () => {
|
||||
const client = createClient();
|
||||
const fetchMock = mock(async () => {
|
||||
const fetchMock = vi.fn(async () => {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
access_token: "new-access",
|
||||
@@ -37,17 +37,17 @@ describe("refreshAccessToken", () => {
|
||||
{ status: 200 },
|
||||
);
|
||||
});
|
||||
(globalThis as { fetch: typeof fetch }).fetch = fetchMock as unknown as typeof fetch;
|
||||
global.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
const result = await refreshAccessToken(baseAuth, client);
|
||||
const result = await refreshAccessToken(baseAuth, client, ANTIGRAVITY_PROVIDER_ID);
|
||||
|
||||
expect(result?.access).toBe("new-access");
|
||||
expect(client.auth.set.mock.calls.length).toBe(0);
|
||||
expect(client.auth.set.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it("persists when Google rotates the refresh token", async () => {
|
||||
const client = createClient();
|
||||
const fetchMock = mock(async () => {
|
||||
const fetchMock = vi.fn(async () => {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
access_token: "next-access",
|
||||
@@ -57,9 +57,9 @@ describe("refreshAccessToken", () => {
|
||||
{ status: 200 },
|
||||
);
|
||||
});
|
||||
(globalThis as { fetch: typeof fetch }).fetch = fetchMock as unknown as typeof fetch;
|
||||
global.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
const result = await refreshAccessToken(baseAuth, client);
|
||||
const result = await refreshAccessToken(baseAuth, client, ANTIGRAVITY_PROVIDER_ID);
|
||||
|
||||
expect(result?.access).toBe("next-access");
|
||||
expect(client.auth.set.mock.calls.length).toBe(1);
|
||||
|
||||
10
vitest.config.ts
Normal file
10
vitest.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
include: ['src/**/*.test.ts'],
|
||||
exclude: ['node_modules', 'dist'],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user