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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-gemini-auth",
3
3
  "module": "index.ts",
4
- "version": "1.4.6",
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.10",
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(): () => Promise<{
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 projectFromEnv = process.env.OPENCODE_GEMINI_PROJECT_ID?.trim() ?? "";
28
- const googleProjectFromEnv =
29
- process.env.GOOGLE_CLOUD_PROJECT?.trim() ??
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(provider);
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;