opencode-antigravity-img 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/bun.lock +73 -0
- package/index.ts +1 -0
- package/package.json +37 -0
- package/src/api.ts +303 -0
- package/src/constants.ts +42 -0
- package/src/index.ts +211 -0
- package/src/types.ts +105 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# opencode-antigravity-img
|
|
2
|
+
|
|
3
|
+
OpenCode plugin for generating images using Gemini 3 Pro Image model via Google's Antigravity/CloudCode API.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- [OpenCode](https://github.com/sst/opencode) installed
|
|
8
|
+
- Google One AI Premium subscription
|
|
9
|
+
- Authentication via [opencode-antigravity-auth](https://www.npmjs.com/package/opencode-antigravity-auth) plugin
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
1. First, install and configure the authentication plugin:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Add to your opencode.json
|
|
17
|
+
"plugin": [
|
|
18
|
+
"opencode-antigravity-auth",
|
|
19
|
+
"opencode-antigravity-img"
|
|
20
|
+
]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
2. Run the auth plugin to authenticate with your Google account:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
opencode
|
|
27
|
+
# Use the authenticate command from antigravity-auth
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This creates `~/.config/opencode/antigravity-accounts.json` with your credentials.
|
|
31
|
+
|
|
32
|
+
## Tools
|
|
33
|
+
|
|
34
|
+
### generate_image
|
|
35
|
+
|
|
36
|
+
Generate an image from a text prompt.
|
|
37
|
+
|
|
38
|
+
**Arguments:**
|
|
39
|
+
- `prompt` (required): Text description of the image to generate
|
|
40
|
+
- `filename` (optional): Output filename (default: `generated_<timestamp>.png`)
|
|
41
|
+
- `output_dir` (optional): Output directory (default: current working directory)
|
|
42
|
+
|
|
43
|
+
**Example:**
|
|
44
|
+
```
|
|
45
|
+
Generate an image of a sunset over mountains with a lake in the foreground
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Output:**
|
|
49
|
+
- Image file saved to specified path
|
|
50
|
+
- Returns path, size, format, and remaining quota
|
|
51
|
+
|
|
52
|
+
### image_quota
|
|
53
|
+
|
|
54
|
+
Check the remaining quota for the Gemini 3 Pro Image model.
|
|
55
|
+
|
|
56
|
+
**Arguments:** None
|
|
57
|
+
|
|
58
|
+
**Output:**
|
|
59
|
+
- Visual progress bar showing remaining quota percentage
|
|
60
|
+
- Time until quota resets
|
|
61
|
+
|
|
62
|
+
## Image Details
|
|
63
|
+
|
|
64
|
+
- **Model**: Gemini 3 Pro Image
|
|
65
|
+
- **Resolution**: 1408x768 pixels
|
|
66
|
+
- **Format**: JPEG (typically 600KB - 1MB)
|
|
67
|
+
- **Generation time**: 10-30 seconds
|
|
68
|
+
|
|
69
|
+
## Quota
|
|
70
|
+
|
|
71
|
+
Image generation uses a separate quota from text models. The quota typically resets daily. Use the `image_quota` tool to check your remaining quota before generating images.
|
|
72
|
+
|
|
73
|
+
## Troubleshooting
|
|
74
|
+
|
|
75
|
+
### "No Antigravity account found"
|
|
76
|
+
|
|
77
|
+
Make sure you've:
|
|
78
|
+
1. Installed `opencode-antigravity-auth`
|
|
79
|
+
2. Authenticated with your Google account
|
|
80
|
+
3. The credentials file exists at `~/.config/opencode/antigravity-accounts.json`
|
|
81
|
+
|
|
82
|
+
### "Rate limited" or generation fails
|
|
83
|
+
|
|
84
|
+
- Wait a few seconds and try again
|
|
85
|
+
- Check your quota with `image_quota`
|
|
86
|
+
- The plugin automatically tries multiple endpoints
|
|
87
|
+
|
|
88
|
+
### Slow generation
|
|
89
|
+
|
|
90
|
+
Image generation typically takes 10-30 seconds. This is normal due to the complexity of image synthesis.
|
|
91
|
+
|
|
92
|
+
## API Endpoints
|
|
93
|
+
|
|
94
|
+
The plugin uses Google's CloudCode API with fallback endpoints:
|
|
95
|
+
1. `https://daily-cloudcode-pa.googleapis.com` (primary)
|
|
96
|
+
2. `https://daily-cloudcode-pa.sandbox.googleapis.com` (fallback)
|
|
97
|
+
3. `https://cloudcode-pa.googleapis.com` (production)
|
|
98
|
+
|
|
99
|
+
## Related Plugins
|
|
100
|
+
|
|
101
|
+
- [opencode-antigravity-auth](https://www.npmjs.com/package/opencode-antigravity-auth) - Authentication (required)
|
|
102
|
+
- [opencode-antigravity-quota](https://www.npmjs.com/package/opencode-antigravity-quota) - Text model quota checking
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
package/bun.lock
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "opencode-antigravity-img",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@opencode-ai/plugin": "^1.1.15",
|
|
9
|
+
"opencode-antigravity-auth": "^1.2.8",
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/bun": "latest",
|
|
13
|
+
"@types/node": "^22.0.0",
|
|
14
|
+
"typescript": "^5",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
"packages": {
|
|
19
|
+
"@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=="],
|
|
20
|
+
|
|
21
|
+
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.1.21", "", { "dependencies": { "@opencode-ai/sdk": "1.1.21", "zod": "4.1.8" } }, "sha512-oAWVlKG7LACGFYawfdHGMN6e+6lyN6F+zPVncFUB99BrTl/TjELE5gTZwU7MalGpjwfU77yslBOZm4BXVAYGvw=="],
|
|
22
|
+
|
|
23
|
+
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.21", "", {}, "sha512-4M6lBjRPlPz99Rb5rS5ZqKrb0UDDxOT9VTG06JpNxvA7ynTd8C50ckc2NGzWtvjarmxfaAk1VeuBYN/cq2pIKQ=="],
|
|
24
|
+
|
|
25
|
+
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
|
26
|
+
|
|
27
|
+
"@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
|
|
28
|
+
|
|
29
|
+
"@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="],
|
|
30
|
+
|
|
31
|
+
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
|
|
32
|
+
|
|
33
|
+
"@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="],
|
|
34
|
+
|
|
35
|
+
"@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="],
|
|
36
|
+
|
|
37
|
+
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
|
38
|
+
|
|
39
|
+
"@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="],
|
|
40
|
+
|
|
41
|
+
"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=="],
|
|
42
|
+
|
|
43
|
+
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
|
44
|
+
|
|
45
|
+
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
|
46
|
+
|
|
47
|
+
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
|
48
|
+
|
|
49
|
+
"hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="],
|
|
50
|
+
|
|
51
|
+
"jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
|
52
|
+
|
|
53
|
+
"opencode-antigravity-auth": ["opencode-antigravity-auth@1.2.8", "", { "dependencies": { "@openauthjs/openauth": "^0.4.3", "proper-lockfile": "^4.1.2", "xdg-basedir": "^5.1.0", "zod": "^3.24.0" }, "peerDependencies": { "typescript": "^5" } }, "sha512-ZLcZdUL3IBUM96WSsclu/4vR0uZLfDBF77b2XKEZcyt5dhOMbJouEkPnp8BEtV1Pu9oFl4eiVcfML7e2JMmrNg=="],
|
|
54
|
+
|
|
55
|
+
"proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
|
|
56
|
+
|
|
57
|
+
"retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
|
|
58
|
+
|
|
59
|
+
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
|
60
|
+
|
|
61
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
62
|
+
|
|
63
|
+
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
|
64
|
+
|
|
65
|
+
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
|
|
66
|
+
|
|
67
|
+
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
|
68
|
+
|
|
69
|
+
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
|
70
|
+
|
|
71
|
+
"opencode-antigravity-auth/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
|
72
|
+
}
|
|
73
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { plugin, default } from "./src/index";
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-antigravity-img",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode plugin for Gemini image generation via Antigravity/CloudCode API",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "bun test",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"opencode",
|
|
13
|
+
"plugin",
|
|
14
|
+
"antigravity",
|
|
15
|
+
"gemini",
|
|
16
|
+
"image",
|
|
17
|
+
"generation",
|
|
18
|
+
"google-cloud"
|
|
19
|
+
],
|
|
20
|
+
"author": "Lorenzo Becchi (ominiverdi)",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/ominiverdi/opencode-antigravity-img.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/ominiverdi/opencode-antigravity-img#readme",
|
|
27
|
+
"private": false,
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/bun": "latest",
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
31
|
+
"typescript": "^5"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@opencode-ai/plugin": "^1.1.15",
|
|
35
|
+
"opencode-antigravity-auth": "^1.2.8"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ANTIGRAVITY_CLIENT_ID,
|
|
3
|
+
ANTIGRAVITY_CLIENT_SECRET,
|
|
4
|
+
GOOGLE_TOKEN_URL,
|
|
5
|
+
CLOUDCODE_BASE_URL,
|
|
6
|
+
CLOUDCODE_FALLBACK_URLS,
|
|
7
|
+
CLOUDCODE_METADATA,
|
|
8
|
+
IMAGE_MODEL,
|
|
9
|
+
IMAGE_GENERATION_TIMEOUT_MS,
|
|
10
|
+
} from "./constants";
|
|
11
|
+
import type {
|
|
12
|
+
Account,
|
|
13
|
+
TokenResponse,
|
|
14
|
+
LoadCodeAssistResponse,
|
|
15
|
+
CloudCodeQuotaResponse,
|
|
16
|
+
GenerateContentResponse,
|
|
17
|
+
ImageGenerationResult,
|
|
18
|
+
QuotaInfo,
|
|
19
|
+
} from "./types";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Refresh an access token using the refresh token
|
|
23
|
+
*/
|
|
24
|
+
export async function refreshAccessToken(refreshToken: string): Promise<string> {
|
|
25
|
+
const params = new URLSearchParams({
|
|
26
|
+
client_id: ANTIGRAVITY_CLIENT_ID,
|
|
27
|
+
client_secret: ANTIGRAVITY_CLIENT_SECRET,
|
|
28
|
+
refresh_token: refreshToken,
|
|
29
|
+
grant_type: "refresh_token",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
35
|
+
body: params.toString(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`Token refresh failed (${response.status})`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const data = (await response.json()) as TokenResponse;
|
|
43
|
+
return data.access_token;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load code assist info to get project ID
|
|
48
|
+
*/
|
|
49
|
+
export async function loadCodeAssist(accessToken: string): Promise<LoadCodeAssistResponse> {
|
|
50
|
+
const response = await fetch(`${CLOUDCODE_BASE_URL}/v1internal:loadCodeAssist`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
Authorization: `Bearer ${accessToken}`,
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
"User-Agent": "antigravity",
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify({ metadata: CLOUDCODE_METADATA }),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`loadCodeAssist failed (${response.status})`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (await response.json()) as LoadCodeAssistResponse;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extract project ID from cloudaicompanionProject field
|
|
69
|
+
*/
|
|
70
|
+
export function extractProjectId(project: string | { id?: string } | undefined): string | undefined {
|
|
71
|
+
if (!project) return undefined;
|
|
72
|
+
if (typeof project === "string") return project;
|
|
73
|
+
return project.id;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Fetch available models with quota info
|
|
78
|
+
*/
|
|
79
|
+
export async function fetchAvailableModels(
|
|
80
|
+
accessToken: string,
|
|
81
|
+
projectId?: string
|
|
82
|
+
): Promise<CloudCodeQuotaResponse> {
|
|
83
|
+
const payload = projectId ? { project: projectId } : {};
|
|
84
|
+
|
|
85
|
+
const response = await fetch(`${CLOUDCODE_BASE_URL}/v1internal:fetchAvailableModels`, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: {
|
|
88
|
+
Authorization: `Bearer ${accessToken}`,
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
"User-Agent": "antigravity",
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(payload),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(`fetchAvailableModels failed (${response.status})`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (await response.json()) as CloudCodeQuotaResponse;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get quota info for the image model
|
|
104
|
+
*/
|
|
105
|
+
export async function getImageModelQuota(account: Account): Promise<QuotaInfo | null> {
|
|
106
|
+
try {
|
|
107
|
+
const accessToken = await refreshAccessToken(account.refreshToken);
|
|
108
|
+
let projectId = account.projectId || account.managedProjectId;
|
|
109
|
+
|
|
110
|
+
if (!projectId) {
|
|
111
|
+
const codeAssist = await loadCodeAssist(accessToken);
|
|
112
|
+
projectId = extractProjectId(codeAssist.cloudaicompanionProject);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const models = await fetchAvailableModels(accessToken, projectId);
|
|
116
|
+
const imageModel = models.models?.[IMAGE_MODEL];
|
|
117
|
+
|
|
118
|
+
if (!imageModel?.quotaInfo) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const quota = imageModel.quotaInfo;
|
|
123
|
+
const remainingPercent = (quota.remainingFraction ?? 0) * 100;
|
|
124
|
+
const resetTime = quota.resetTime || "";
|
|
125
|
+
|
|
126
|
+
// Calculate reset in human readable
|
|
127
|
+
let resetIn = "N/A";
|
|
128
|
+
if (resetTime) {
|
|
129
|
+
const resetDate = new Date(resetTime);
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const diffMs = resetDate.getTime() - now;
|
|
132
|
+
if (diffMs > 0) {
|
|
133
|
+
const hours = Math.floor(diffMs / 3600000);
|
|
134
|
+
const mins = Math.floor((diffMs % 3600000) / 60000);
|
|
135
|
+
resetIn = `${hours}h ${mins}m`;
|
|
136
|
+
} else {
|
|
137
|
+
resetIn = "now";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
modelName: imageModel.displayName || IMAGE_MODEL,
|
|
143
|
+
remainingPercent,
|
|
144
|
+
resetTime,
|
|
145
|
+
resetIn,
|
|
146
|
+
};
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Generate an image using the Gemini 3 Pro Image model
|
|
154
|
+
*/
|
|
155
|
+
export async function generateImage(
|
|
156
|
+
account: Account,
|
|
157
|
+
prompt: string
|
|
158
|
+
): Promise<ImageGenerationResult> {
|
|
159
|
+
try {
|
|
160
|
+
// Get access token
|
|
161
|
+
const accessToken = await refreshAccessToken(account.refreshToken);
|
|
162
|
+
|
|
163
|
+
// Get project ID
|
|
164
|
+
let projectId = account.projectId || account.managedProjectId;
|
|
165
|
+
if (!projectId) {
|
|
166
|
+
const codeAssist = await loadCodeAssist(accessToken);
|
|
167
|
+
projectId = extractProjectId(codeAssist.cloudaicompanionProject);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!projectId) {
|
|
171
|
+
return { success: false, error: "Could not determine project ID" };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Build request
|
|
175
|
+
const requestBody = {
|
|
176
|
+
project: projectId,
|
|
177
|
+
requestId: `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
178
|
+
model: IMAGE_MODEL,
|
|
179
|
+
userAgent: "antigravity",
|
|
180
|
+
requestType: "agent",
|
|
181
|
+
request: {
|
|
182
|
+
contents: [{ role: "user", parts: [{ text: prompt }] }],
|
|
183
|
+
session_id: `sess_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
184
|
+
generationConfig: {
|
|
185
|
+
responseModalities: ["TEXT", "IMAGE"],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Try each endpoint
|
|
191
|
+
for (const baseUrl of CLOUDCODE_FALLBACK_URLS) {
|
|
192
|
+
try {
|
|
193
|
+
const controller = new AbortController();
|
|
194
|
+
const timeout = setTimeout(() => controller.abort(), IMAGE_GENERATION_TIMEOUT_MS);
|
|
195
|
+
|
|
196
|
+
const response = await fetch(
|
|
197
|
+
`${baseUrl}/v1internal:streamGenerateContent?alt=sse`,
|
|
198
|
+
{
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: {
|
|
201
|
+
Authorization: `Bearer ${accessToken}`,
|
|
202
|
+
"Content-Type": "application/json",
|
|
203
|
+
"User-Agent": "antigravity",
|
|
204
|
+
},
|
|
205
|
+
body: JSON.stringify(requestBody),
|
|
206
|
+
signal: controller.signal,
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
clearTimeout(timeout);
|
|
211
|
+
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
if (response.status === 429) {
|
|
214
|
+
// Rate limited, try next endpoint
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const errorText = await response.text();
|
|
218
|
+
return { success: false, error: `HTTP ${response.status}: ${errorText.slice(0, 200)}` };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Parse SSE response
|
|
222
|
+
const text = await response.text();
|
|
223
|
+
const result = parseSSEResponse(text);
|
|
224
|
+
|
|
225
|
+
if (result.success && result.imageData) {
|
|
226
|
+
// Get updated quota info
|
|
227
|
+
const quota = await getImageModelQuota(account);
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
...result,
|
|
231
|
+
quota: quota
|
|
232
|
+
? {
|
|
233
|
+
remainingPercent: quota.remainingPercent,
|
|
234
|
+
resetTime: quota.resetTime,
|
|
235
|
+
}
|
|
236
|
+
: undefined,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return result;
|
|
241
|
+
} catch (err) {
|
|
242
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
243
|
+
continue; // Timeout, try next endpoint
|
|
244
|
+
}
|
|
245
|
+
// Network error, try next endpoint
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { success: false, error: "All endpoints failed" };
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return {
|
|
253
|
+
success: false,
|
|
254
|
+
error: error instanceof Error ? error.message : String(error),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Parse SSE response and extract image data
|
|
261
|
+
*/
|
|
262
|
+
function parseSSEResponse(text: string): ImageGenerationResult {
|
|
263
|
+
const lines = text.split("\n");
|
|
264
|
+
|
|
265
|
+
for (const line of lines) {
|
|
266
|
+
if (!line.startsWith("data: ")) continue;
|
|
267
|
+
|
|
268
|
+
const jsonStr = line.slice(6);
|
|
269
|
+
if (jsonStr === "[DONE]") continue;
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const data = JSON.parse(jsonStr) as GenerateContentResponse;
|
|
273
|
+
|
|
274
|
+
// Check for error
|
|
275
|
+
if (data.error) {
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
error: `${data.error.code}: ${data.error.message}`,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Look for image in response
|
|
283
|
+
const candidates = data.response?.candidates || [];
|
|
284
|
+
for (const candidate of candidates) {
|
|
285
|
+
const parts = candidate.content?.parts || [];
|
|
286
|
+
for (const part of parts) {
|
|
287
|
+
if (part.inlineData?.data && part.inlineData.mimeType?.startsWith("image/")) {
|
|
288
|
+
return {
|
|
289
|
+
success: true,
|
|
290
|
+
imageData: part.inlineData.data,
|
|
291
|
+
mimeType: part.inlineData.mimeType,
|
|
292
|
+
sizeBytes: Math.round((part.inlineData.data.length * 3) / 4), // Approximate decoded size
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
// Skip unparseable lines
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return { success: false, error: "No image in response" };
|
|
303
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
// OAuth credentials (same as antigravity-auth plugin)
|
|
5
|
+
export const ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
6
|
+
export const ANTIGRAVITY_CLIENT_SECRET = "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";
|
|
7
|
+
export const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
8
|
+
|
|
9
|
+
// CloudCode API
|
|
10
|
+
export const CLOUDCODE_BASE_URL = "https://daily-cloudcode-pa.googleapis.com";
|
|
11
|
+
export const CLOUDCODE_FALLBACK_URLS = [
|
|
12
|
+
"https://daily-cloudcode-pa.googleapis.com",
|
|
13
|
+
"https://daily-cloudcode-pa.sandbox.googleapis.com",
|
|
14
|
+
"https://cloudcode-pa.googleapis.com",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export const CLOUDCODE_METADATA = {
|
|
18
|
+
ideType: "ANTIGRAVITY",
|
|
19
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
20
|
+
pluginType: "GEMINI",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Image generation
|
|
24
|
+
export const IMAGE_MODEL = "gemini-3-pro-image";
|
|
25
|
+
export const IMAGE_GENERATION_TIMEOUT_MS = 120_000;
|
|
26
|
+
|
|
27
|
+
// Config file paths
|
|
28
|
+
export const CONFIG_PATHS = [
|
|
29
|
+
join(homedir(), ".config", "opencode", "antigravity-accounts.json"),
|
|
30
|
+
join(homedir(), ".opencode", "antigravity-accounts.json"),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Command file for opencode discovery
|
|
34
|
+
export const COMMAND_DIR = join(homedir(), ".config", "opencode", "commands");
|
|
35
|
+
export const COMMAND_FILE = join(COMMAND_DIR, "generate-image.md");
|
|
36
|
+
export const COMMAND_CONTENT = `# Generate Image
|
|
37
|
+
|
|
38
|
+
Generate an image using Gemini 3 Pro Image model.
|
|
39
|
+
|
|
40
|
+
Prompt: $PROMPT
|
|
41
|
+
Output filename (optional): $FILENAME
|
|
42
|
+
`;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { type Plugin, tool } from "@opencode-ai/plugin";
|
|
2
|
+
import * as fs from "fs/promises";
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { CONFIG_PATHS, COMMAND_DIR, COMMAND_FILE, COMMAND_CONTENT, IMAGE_MODEL } from "./constants";
|
|
6
|
+
import type { AccountsConfig, Account } from "./types";
|
|
7
|
+
import { generateImage, getImageModelQuota } from "./api";
|
|
8
|
+
|
|
9
|
+
// Create command file for opencode discovery
|
|
10
|
+
try {
|
|
11
|
+
if (!existsSync(COMMAND_DIR)) {
|
|
12
|
+
mkdirSync(COMMAND_DIR, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
if (!existsSync(COMMAND_FILE)) {
|
|
15
|
+
writeFileSync(COMMAND_FILE, COMMAND_CONTENT, "utf-8");
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
// Non-fatal if command file creation fails
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load accounts from config file
|
|
23
|
+
*/
|
|
24
|
+
async function loadAccounts(): Promise<AccountsConfig | null> {
|
|
25
|
+
for (const configPath of CONFIG_PATHS) {
|
|
26
|
+
if (existsSync(configPath)) {
|
|
27
|
+
try {
|
|
28
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
29
|
+
return JSON.parse(content) as AccountsConfig;
|
|
30
|
+
} catch {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the first available account
|
|
40
|
+
*/
|
|
41
|
+
async function getAccount(): Promise<Account | null> {
|
|
42
|
+
const config = await loadAccounts();
|
|
43
|
+
if (!config?.accounts?.length) return null;
|
|
44
|
+
return config.accounts[0] || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Format file size for display
|
|
49
|
+
*/
|
|
50
|
+
function formatSize(bytes: number): string {
|
|
51
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
52
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
53
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Format quota for display
|
|
58
|
+
*/
|
|
59
|
+
function formatQuota(percent: number): string {
|
|
60
|
+
if (percent <= 10) return `${percent.toFixed(0)}% (low)`;
|
|
61
|
+
if (percent <= 30) return `${percent.toFixed(0)}% (medium)`;
|
|
62
|
+
return `${percent.toFixed(0)}%`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const plugin: Plugin = async (ctx) => {
|
|
66
|
+
return {
|
|
67
|
+
tool: {
|
|
68
|
+
/**
|
|
69
|
+
* Generate an image using Gemini 3 Pro Image
|
|
70
|
+
*/
|
|
71
|
+
generate_image: tool({
|
|
72
|
+
description:
|
|
73
|
+
"Generate an image using Gemini 3 Pro Image model. " +
|
|
74
|
+
"Provide a text prompt describing the image you want. " +
|
|
75
|
+
"Returns the path to the generated image file.",
|
|
76
|
+
args: {
|
|
77
|
+
prompt: tool.schema.string().describe("Text description of the image to generate"),
|
|
78
|
+
filename: tool.schema
|
|
79
|
+
.string()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Output filename (default: generated_<timestamp>.png)"),
|
|
82
|
+
output_dir: tool.schema
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Output directory (default: current working directory)"),
|
|
86
|
+
},
|
|
87
|
+
async execute(args, context) {
|
|
88
|
+
const { prompt, filename, output_dir } = args;
|
|
89
|
+
|
|
90
|
+
if (!prompt?.trim()) {
|
|
91
|
+
return "Error: Please provide a prompt describing the image to generate.";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Get account
|
|
95
|
+
const account = await getAccount();
|
|
96
|
+
if (!account) {
|
|
97
|
+
return (
|
|
98
|
+
"Error: No Antigravity account found.\n\n" +
|
|
99
|
+
"Please install and configure opencode-antigravity-auth first:\n" +
|
|
100
|
+
" 1. Add 'opencode-antigravity-auth' to your opencode plugins\n" +
|
|
101
|
+
" 2. Authenticate with your Google account\n\n" +
|
|
102
|
+
`Checked paths:\n${CONFIG_PATHS.map((p) => ` - ${p}`).join("\n")}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
context.metadata({ title: "Generating image..." });
|
|
107
|
+
|
|
108
|
+
// Generate image
|
|
109
|
+
const result = await generateImage(account, prompt);
|
|
110
|
+
|
|
111
|
+
if (!result.success || !result.imageData) {
|
|
112
|
+
return `Error generating image: ${result.error || "Unknown error"}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Determine output path
|
|
116
|
+
const dir = output_dir || ctx.directory;
|
|
117
|
+
const ext = result.mimeType === "image/png" ? "png" : "jpg";
|
|
118
|
+
const name = filename || `generated_${Date.now()}.${ext}`;
|
|
119
|
+
const outputPath = join(dir, name);
|
|
120
|
+
|
|
121
|
+
// Ensure directory exists
|
|
122
|
+
const dirPath = dirname(outputPath);
|
|
123
|
+
if (!existsSync(dirPath)) {
|
|
124
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Decode and save image
|
|
128
|
+
const imageBuffer = Buffer.from(result.imageData, "base64");
|
|
129
|
+
await fs.writeFile(outputPath, imageBuffer);
|
|
130
|
+
|
|
131
|
+
const sizeStr = formatSize(imageBuffer.length);
|
|
132
|
+
|
|
133
|
+
context.metadata({
|
|
134
|
+
title: "Image generated",
|
|
135
|
+
metadata: {
|
|
136
|
+
path: outputPath,
|
|
137
|
+
size: sizeStr,
|
|
138
|
+
format: result.mimeType,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Build response
|
|
143
|
+
let response = `Image generated successfully!\n\n`;
|
|
144
|
+
response += `Path: ${outputPath}\n`;
|
|
145
|
+
response += `Size: ${sizeStr}\n`;
|
|
146
|
+
response += `Format: ${result.mimeType}\n`;
|
|
147
|
+
|
|
148
|
+
if (result.quota) {
|
|
149
|
+
response += `\nQuota: ${formatQuota(result.quota.remainingPercent)} remaining`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return response;
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check quota for image generation model
|
|
158
|
+
*/
|
|
159
|
+
image_quota: tool({
|
|
160
|
+
description:
|
|
161
|
+
"Check the remaining quota for the Gemini 3 Pro Image model. " +
|
|
162
|
+
"Shows percentage remaining and time until reset.",
|
|
163
|
+
args: {},
|
|
164
|
+
async execute(args, context) {
|
|
165
|
+
const account = await getAccount();
|
|
166
|
+
if (!account) {
|
|
167
|
+
return (
|
|
168
|
+
"Error: No Antigravity account found.\n" +
|
|
169
|
+
"Please configure opencode-antigravity-auth first."
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
context.metadata({ title: "Checking quota..." });
|
|
174
|
+
|
|
175
|
+
const quota = await getImageModelQuota(account);
|
|
176
|
+
|
|
177
|
+
if (!quota) {
|
|
178
|
+
return "Error: Could not fetch quota information.";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
context.metadata({
|
|
182
|
+
title: "Quota",
|
|
183
|
+
metadata: {
|
|
184
|
+
remaining: `${quota.remainingPercent.toFixed(0)}%`,
|
|
185
|
+
resetIn: quota.resetIn,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Visual progress bar
|
|
190
|
+
const barWidth = 20;
|
|
191
|
+
const filled = Math.round((quota.remainingPercent / 100) * barWidth);
|
|
192
|
+
const empty = barWidth - filled;
|
|
193
|
+
const bar = "#".repeat(filled) + ".".repeat(empty);
|
|
194
|
+
|
|
195
|
+
let response = `${quota.modelName}\n\n`;
|
|
196
|
+
response += `[${bar}] ${quota.remainingPercent.toFixed(0)}% remaining\n`;
|
|
197
|
+
response += `Resets in: ${quota.resetIn}`;
|
|
198
|
+
|
|
199
|
+
if (quota.resetTime) {
|
|
200
|
+
const resetDate = new Date(quota.resetTime);
|
|
201
|
+
response += ` (at ${resetDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })})`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return response;
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export default plugin;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Account configuration (from antigravity-accounts.json)
|
|
2
|
+
export interface Account {
|
|
3
|
+
email: string;
|
|
4
|
+
refreshToken: string;
|
|
5
|
+
accessToken?: string;
|
|
6
|
+
projectId?: string;
|
|
7
|
+
managedProjectId?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface AccountsConfig {
|
|
11
|
+
accounts: Account[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// API responses
|
|
15
|
+
export interface TokenResponse {
|
|
16
|
+
access_token: string;
|
|
17
|
+
expires_in: number;
|
|
18
|
+
token_type: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LoadCodeAssistResponse {
|
|
22
|
+
cloudaicompanionProject?: string | { id?: string };
|
|
23
|
+
currentTier?: { id?: string; name?: string };
|
|
24
|
+
paidTier?: { id?: string; name?: string };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CloudCodeQuotaResponse {
|
|
28
|
+
models?: Record<string, ModelInfo>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ModelInfo {
|
|
32
|
+
displayName?: string;
|
|
33
|
+
model?: string;
|
|
34
|
+
quotaInfo?: {
|
|
35
|
+
remainingFraction?: number;
|
|
36
|
+
resetTime?: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Image generation
|
|
41
|
+
export interface GenerateContentRequest {
|
|
42
|
+
project: string;
|
|
43
|
+
requestId: string;
|
|
44
|
+
model: string;
|
|
45
|
+
userAgent: string;
|
|
46
|
+
requestType: string;
|
|
47
|
+
request: {
|
|
48
|
+
contents: Array<{
|
|
49
|
+
role: string;
|
|
50
|
+
parts: Array<{ text: string }>;
|
|
51
|
+
}>;
|
|
52
|
+
session_id: string;
|
|
53
|
+
generationConfig: {
|
|
54
|
+
responseModalities: string[];
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface GenerateContentResponse {
|
|
60
|
+
response?: {
|
|
61
|
+
candidates?: Array<{
|
|
62
|
+
content?: {
|
|
63
|
+
parts?: Array<{
|
|
64
|
+
text?: string;
|
|
65
|
+
thought?: boolean;
|
|
66
|
+
inlineData?: {
|
|
67
|
+
mimeType: string;
|
|
68
|
+
data: string;
|
|
69
|
+
};
|
|
70
|
+
}>;
|
|
71
|
+
};
|
|
72
|
+
finishReason?: string;
|
|
73
|
+
}>;
|
|
74
|
+
usageMetadata?: {
|
|
75
|
+
promptTokenCount?: number;
|
|
76
|
+
candidatesTokenCount?: number;
|
|
77
|
+
totalTokenCount?: number;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
error?: {
|
|
81
|
+
code: number;
|
|
82
|
+
message: string;
|
|
83
|
+
status: string;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ImageGenerationResult {
|
|
88
|
+
success: boolean;
|
|
89
|
+
imagePath?: string;
|
|
90
|
+
imageData?: string; // base64
|
|
91
|
+
mimeType?: string;
|
|
92
|
+
sizeBytes?: number;
|
|
93
|
+
error?: string;
|
|
94
|
+
quota?: {
|
|
95
|
+
remainingPercent: number;
|
|
96
|
+
resetTime: string;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface QuotaInfo {
|
|
101
|
+
modelName: string;
|
|
102
|
+
remainingPercent: number;
|
|
103
|
+
resetTime: string;
|
|
104
|
+
resetIn: string;
|
|
105
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "."
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*", "index.ts"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|