opencode-gemini-auth 1.4.6 → 1.4.7
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/package.json +2 -2
- package/src/plugin/oauth-authorize.ts +7 -7
- package/src/plugin/project.test.ts +25 -0
- package/src/plugin/provider.test.ts +59 -0
- package/src/plugin/provider.ts +55 -0
- package/src/plugin.ts +9 -20
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-gemini-auth",
|
|
3
3
|
"module": "index.ts",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.7",
|
|
5
5
|
"author": "jenslys",
|
|
6
6
|
"repository": "https://github.com/jenslys/opencode-gemini-auth",
|
|
7
7
|
"files": [
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typescript": "^5.9.3"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@opencode-ai/plugin": "^1.2.
|
|
25
|
+
"@opencode-ai/plugin": "^1.2.20",
|
|
26
26
|
"@openauthjs/openauth": "^0.4.3"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -4,13 +4,16 @@ import { authorizeGemini, exchangeGeminiWithVerifier } from "../gemini/oauth";
|
|
|
4
4
|
import type { GeminiTokenExchangeResult } from "../gemini/oauth";
|
|
5
5
|
import { isGeminiDebugEnabled, logGeminiDebugMessage } from "./debug";
|
|
6
6
|
import { resolveProjectContextFromAccessToken } from "./project";
|
|
7
|
+
import { resolveConfiguredProjectId } from "./provider";
|
|
7
8
|
import { startOAuthListener, type OAuthListener } from "./server";
|
|
8
9
|
import type { OAuthAuthDetails } from "./types";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Builds the OAuth authorize callback used by plugin auth methods.
|
|
12
13
|
*/
|
|
13
|
-
export function createOAuthAuthorizeMethod(
|
|
14
|
+
export function createOAuthAuthorizeMethod(options?: {
|
|
15
|
+
getConfiguredProjectId?: () => string | undefined;
|
|
16
|
+
}): () => Promise<{
|
|
14
17
|
url: string;
|
|
15
18
|
instructions: string;
|
|
16
19
|
method: string;
|
|
@@ -24,12 +27,9 @@ export function createOAuthAuthorizeMethod(): () => Promise<{
|
|
|
24
27
|
return result;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
process.env.GOOGLE_CLOUD_PROJECT_ID?.trim() ??
|
|
31
|
-
"";
|
|
32
|
-
const configuredProjectId = projectFromEnv || googleProjectFromEnv || undefined;
|
|
30
|
+
const configuredProjectId = resolveConfiguredProjectId({
|
|
31
|
+
configProjectId: options?.getConfiguredProjectId?.(),
|
|
32
|
+
});
|
|
33
33
|
|
|
34
34
|
try {
|
|
35
35
|
const authSnapshot = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import { formatRefreshParts } from "./auth";
|
|
3
4
|
import { resolveProjectContextFromAccessToken } from "./project";
|
|
4
5
|
import type { OAuthAuthDetails } from "./types";
|
|
5
6
|
|
|
@@ -109,4 +110,28 @@ describe("resolveProjectContextFromAccessToken", () => {
|
|
|
109
110
|
resolveProjectContextFromAccessToken(baseAuth, baseAuth.access ?? ""),
|
|
110
111
|
).rejects.toThrow("Google Gemini requires a Google Cloud project");
|
|
111
112
|
});
|
|
113
|
+
|
|
114
|
+
it("prefers a configured project id over a persisted managed project id", async () => {
|
|
115
|
+
const authWithManagedProject: OAuthAuthDetails = {
|
|
116
|
+
...baseAuth,
|
|
117
|
+
refresh: formatRefreshParts({
|
|
118
|
+
refreshToken: "refresh-token",
|
|
119
|
+
managedProjectId: "managed-project",
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const fetchMock = mock(async () => {
|
|
124
|
+
throw new Error("should not fetch project context when a configured project id exists");
|
|
125
|
+
});
|
|
126
|
+
(globalThis as { fetch: typeof fetch }).fetch = fetchMock as unknown as typeof fetch;
|
|
127
|
+
|
|
128
|
+
const result = await resolveProjectContextFromAccessToken(
|
|
129
|
+
authWithManagedProject,
|
|
130
|
+
authWithManagedProject.access ?? "",
|
|
131
|
+
"configured-project",
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(result.effectiveProjectId).toBe("configured-project");
|
|
135
|
+
expect(fetchMock.mock.calls.length).toBe(0);
|
|
136
|
+
});
|
|
112
137
|
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import type { Config } from "@opencode-ai/sdk";
|
|
3
|
+
|
|
4
|
+
import { resolveConfiguredProjectId, resolveConfiguredProjectIdFromConfig } from "./provider";
|
|
5
|
+
import type { Provider } from "./types";
|
|
6
|
+
|
|
7
|
+
describe("resolveConfiguredProjectId", () => {
|
|
8
|
+
it("reads project id from provider options", () => {
|
|
9
|
+
const provider = {
|
|
10
|
+
id: "google",
|
|
11
|
+
name: "Google",
|
|
12
|
+
source: "config",
|
|
13
|
+
env: [],
|
|
14
|
+
options: {
|
|
15
|
+
projectId: " provider-project ",
|
|
16
|
+
},
|
|
17
|
+
models: {},
|
|
18
|
+
} satisfies Provider;
|
|
19
|
+
|
|
20
|
+
expect(
|
|
21
|
+
resolveConfiguredProjectId({
|
|
22
|
+
provider,
|
|
23
|
+
env: {},
|
|
24
|
+
}),
|
|
25
|
+
).toBe("provider-project");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("falls back to the top-level config project id when provider options are unavailable", () => {
|
|
29
|
+
const config = {
|
|
30
|
+
provider: {
|
|
31
|
+
google: {
|
|
32
|
+
options: {
|
|
33
|
+
projectId: "config-project",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
} satisfies Config;
|
|
38
|
+
|
|
39
|
+
expect(resolveConfiguredProjectIdFromConfig(config)).toBe("config-project");
|
|
40
|
+
expect(
|
|
41
|
+
resolveConfiguredProjectId({
|
|
42
|
+
config,
|
|
43
|
+
env: {},
|
|
44
|
+
}),
|
|
45
|
+
).toBe("config-project");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("prefers OPENCODE_GEMINI_PROJECT_ID over config and google cloud env vars", () => {
|
|
49
|
+
expect(
|
|
50
|
+
resolveConfiguredProjectId({
|
|
51
|
+
configProjectId: "config-project",
|
|
52
|
+
env: {
|
|
53
|
+
OPENCODE_GEMINI_PROJECT_ID: "opencode-project",
|
|
54
|
+
GOOGLE_CLOUD_PROJECT: "google-project",
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
).toBe("opencode-project");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Config } from "@opencode-ai/sdk";
|
|
2
|
+
|
|
3
|
+
import { GEMINI_PROVIDER_ID } from "../constants";
|
|
4
|
+
import type { Provider } from "./types";
|
|
5
|
+
|
|
6
|
+
interface ResolveConfiguredProjectIdInput {
|
|
7
|
+
provider?: Provider | null;
|
|
8
|
+
config?: Config | null;
|
|
9
|
+
configProjectId?: string;
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveConfiguredProjectId(
|
|
14
|
+
input: ResolveConfiguredProjectIdInput = {},
|
|
15
|
+
): string | undefined {
|
|
16
|
+
const env = input.env ?? process.env;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
normalizeProjectId(env.OPENCODE_GEMINI_PROJECT_ID) ??
|
|
20
|
+
resolveConfiguredProjectIdFromProvider(input.provider) ??
|
|
21
|
+
normalizeProjectId(input.configProjectId) ??
|
|
22
|
+
resolveConfiguredProjectIdFromConfig(input.config) ??
|
|
23
|
+
normalizeProjectId(env.GOOGLE_CLOUD_PROJECT) ??
|
|
24
|
+
normalizeProjectId(env.GOOGLE_CLOUD_PROJECT_ID)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resolveConfiguredProjectIdFromProvider(
|
|
29
|
+
provider: Provider | null | undefined,
|
|
30
|
+
): string | undefined {
|
|
31
|
+
if (!provider || typeof provider !== "object") {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return normalizeProjectId(provider.options?.projectId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveConfiguredProjectIdFromConfig(
|
|
38
|
+
config: Config | null | undefined,
|
|
39
|
+
): string | undefined {
|
|
40
|
+
if (!config?.provider || typeof config.provider !== "object") {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const providerConfig = config.provider[GEMINI_PROVIDER_ID];
|
|
45
|
+
return normalizeProjectId(providerConfig?.options?.projectId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeProjectId(value: unknown): string | undefined {
|
|
49
|
+
if (typeof value !== "string") {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const trimmed = value.trim();
|
|
54
|
+
return trimmed || undefined;
|
|
55
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "./plugin/quota";
|
|
10
10
|
import { isGeminiDebugEnabled, logGeminiDebugMessage, startGeminiDebugRequest } from "./plugin/debug";
|
|
11
11
|
import { maybeShowGeminiCapacityToast, maybeShowGeminiTestToast } from "./plugin/notify";
|
|
12
|
+
import { resolveConfiguredProjectId, resolveConfiguredProjectIdFromConfig } from "./plugin/provider";
|
|
12
13
|
import {
|
|
13
14
|
isGenerativeLanguageRequest,
|
|
14
15
|
prepareGeminiRequest,
|
|
@@ -44,6 +45,7 @@ export const GeminiCLIOAuthPlugin = async (
|
|
|
44
45
|
{ client }: PluginContext,
|
|
45
46
|
): Promise<PluginResult> => ({
|
|
46
47
|
config: async (config) => {
|
|
48
|
+
latestGeminiConfiguredProjectId = resolveConfiguredProjectIdFromConfig(config);
|
|
47
49
|
config.command = config.command || {};
|
|
48
50
|
config.command[GEMINI_QUOTA_COMMAND] = {
|
|
49
51
|
description: "Show Gemini Code Assist quota usage",
|
|
@@ -66,7 +68,10 @@ export const GeminiCLIOAuthPlugin = async (
|
|
|
66
68
|
return null;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
const configuredProjectId = resolveConfiguredProjectId(
|
|
71
|
+
const configuredProjectId = resolveConfiguredProjectId({
|
|
72
|
+
provider,
|
|
73
|
+
configProjectId: latestGeminiConfiguredProjectId,
|
|
74
|
+
});
|
|
70
75
|
latestGeminiConfiguredProjectId = configuredProjectId;
|
|
71
76
|
normalizeProviderModelCosts(provider);
|
|
72
77
|
const thinkingConfigDefaults = resolveThinkingConfigDefaults(provider);
|
|
@@ -147,7 +152,9 @@ export const GeminiCLIOAuthPlugin = async (
|
|
|
147
152
|
{
|
|
148
153
|
label: "OAuth with Google (Gemini CLI)",
|
|
149
154
|
type: "oauth",
|
|
150
|
-
authorize: createOAuthAuthorizeMethod(
|
|
155
|
+
authorize: createOAuthAuthorizeMethod({
|
|
156
|
+
getConfiguredProjectId: () => latestGeminiConfiguredProjectId,
|
|
157
|
+
}),
|
|
151
158
|
},
|
|
152
159
|
{
|
|
153
160
|
provider: GEMINI_PROVIDER_ID,
|
|
@@ -161,24 +168,6 @@ export const GeminiCLIOAuthPlugin = async (
|
|
|
161
168
|
export const GoogleOAuthPlugin = GeminiCLIOAuthPlugin;
|
|
162
169
|
const loggedQuotaModelsByProject = new Set<string>();
|
|
163
170
|
|
|
164
|
-
function resolveConfiguredProjectId(provider: Provider): string | undefined {
|
|
165
|
-
const providerOptions =
|
|
166
|
-
provider && typeof provider === "object"
|
|
167
|
-
? ((provider as { options?: Record<string, unknown> }).options ?? undefined)
|
|
168
|
-
: undefined;
|
|
169
|
-
const projectIdFromConfig =
|
|
170
|
-
providerOptions && typeof providerOptions.projectId === "string"
|
|
171
|
-
? providerOptions.projectId.trim()
|
|
172
|
-
: "";
|
|
173
|
-
const projectIdFromEnv = process.env.OPENCODE_GEMINI_PROJECT_ID?.trim() ?? "";
|
|
174
|
-
const googleProjectIdFromEnv =
|
|
175
|
-
process.env.GOOGLE_CLOUD_PROJECT?.trim() ??
|
|
176
|
-
process.env.GOOGLE_CLOUD_PROJECT_ID?.trim() ??
|
|
177
|
-
"";
|
|
178
|
-
|
|
179
|
-
return projectIdFromEnv || projectIdFromConfig || googleProjectIdFromEnv || undefined;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
171
|
function normalizeProviderModelCosts(provider: Provider): void {
|
|
183
172
|
if (!provider.models) {
|
|
184
173
|
return;
|