opencode-gemini-auth 1.4.5 → 1.4.6

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.5",
4
+ "version": "1.4.6",
5
5
  "author": "jenslys",
6
6
  "repository": "https://github.com/jenslys/opencode-gemini-auth",
7
7
  "files": [
@@ -1,3 +1,4 @@
1
1
  export { prepareGeminiRequest } from "./prepare";
2
+ export type { ThinkingConfigDefaults } from "./prepare";
2
3
  export { transformGeminiResponse } from "./response";
3
4
  export { isGenerativeLanguageRequest } from "./shared";
@@ -12,6 +12,11 @@ const MODEL_FALLBACKS: Record<string, string> = {
12
12
  "gemini-2.5-flash-image": "gemini-2.5-flash",
13
13
  };
14
14
 
15
+ export interface ThinkingConfigDefaults {
16
+ provider?: unknown;
17
+ models?: Record<string, unknown>;
18
+ }
19
+
15
20
  /**
16
21
  * Rewrites OpenAI-style requests into Gemini Code Assist request shape.
17
22
  */
@@ -20,6 +25,7 @@ export function prepareGeminiRequest(
20
25
  init: RequestInit | undefined,
21
26
  accessToken: string,
22
27
  projectId: string,
28
+ thinkingConfigDefaults?: ThinkingConfigDefaults,
23
29
  ): {
24
30
  request: RequestInfo;
25
31
  init: RequestInit;
@@ -61,7 +67,13 @@ export function prepareGeminiRequest(
61
67
  let requestIdentifier: string = randomUUID();
62
68
 
63
69
  if (typeof baseInit.body === "string" && baseInit.body) {
64
- const transformed = transformRequestBody(baseInit.body, projectId, effectiveModel);
70
+ const transformed = transformRequestBody(
71
+ baseInit.body,
72
+ projectId,
73
+ effectiveModel,
74
+ rawModel,
75
+ thinkingConfigDefaults,
76
+ );
65
77
  if (transformed.body) {
66
78
  body = transformed.body;
67
79
  requestIdentifier = transformed.userPromptId;
@@ -97,6 +109,8 @@ function transformRequestBody(
97
109
  body: string,
98
110
  projectId: string,
99
111
  effectiveModel: string,
112
+ requestedModel: string,
113
+ thinkingConfigDefaults?: ThinkingConfigDefaults,
100
114
  ): { body?: string; userPromptId: string } {
101
115
  const fallbackId = randomUUID();
102
116
  try {
@@ -115,7 +129,11 @@ function transformRequestBody(
115
129
  const requestPayload = { ...parsedBody };
116
130
  transformOpenAIToolCalls(requestPayload);
117
131
  addThoughtSignaturesToFunctionCalls(requestPayload);
118
- normalizeThinking(requestPayload);
132
+ normalizeThinking(
133
+ requestPayload,
134
+ resolveDefaultThinkingConfig(thinkingConfigDefaults, requestedModel, effectiveModel),
135
+ thinkingConfigDefaults?.provider,
136
+ );
119
137
  normalizeSystemInstruction(requestPayload);
120
138
  normalizeCachedContent(requestPayload);
121
139
  stripThoughtPartsFromHistory(requestPayload);
@@ -139,9 +157,30 @@ function transformRequestBody(
139
157
  }
140
158
  }
141
159
 
142
- function normalizeThinking(requestPayload: Record<string, unknown>): void {
160
+ function resolveDefaultThinkingConfig(
161
+ thinkingConfigDefaults: ThinkingConfigDefaults | undefined,
162
+ requestedModel: string,
163
+ effectiveModel: string,
164
+ ): unknown {
165
+ if (!thinkingConfigDefaults?.models) {
166
+ return undefined;
167
+ }
168
+
169
+ return thinkingConfigDefaults.models[requestedModel] ?? thinkingConfigDefaults.models[effectiveModel];
170
+ }
171
+
172
+ function normalizeThinking(
173
+ requestPayload: Record<string, unknown>,
174
+ modelThinkingConfig: unknown,
175
+ providerThinkingConfig: unknown,
176
+ ): void {
143
177
  const rawGenerationConfig = requestPayload.generationConfig as Record<string, unknown> | undefined;
144
- const normalizedThinking = normalizeThinkingConfig(rawGenerationConfig?.thinkingConfig);
178
+ const hasRequestThinkingConfig =
179
+ !!rawGenerationConfig && Object.prototype.hasOwnProperty.call(rawGenerationConfig, "thinkingConfig");
180
+ const sourceThinkingConfig = hasRequestThinkingConfig
181
+ ? rawGenerationConfig?.thinkingConfig
182
+ : modelThinkingConfig ?? providerThinkingConfig;
183
+ const normalizedThinking = normalizeThinkingConfig(sourceThinkingConfig);
145
184
  if (normalizedThinking) {
146
185
  if (rawGenerationConfig) {
147
186
  rawGenerationConfig.thinkingConfig = normalizedThinking;
@@ -152,7 +191,7 @@ function normalizeThinking(requestPayload: Record<string, unknown>): void {
152
191
  return;
153
192
  }
154
193
 
155
- if (rawGenerationConfig?.thinkingConfig) {
194
+ if (hasRequestThinkingConfig && rawGenerationConfig) {
156
195
  delete rawGenerationConfig.thinkingConfig;
157
196
  requestPayload.generationConfig = rawGenerationConfig;
158
197
  }
@@ -14,13 +14,19 @@ export function normalizeThinkingConfig(config: unknown): ThinkingConfig | undef
14
14
  const includeRaw = record.includeThoughts ?? record.include_thoughts;
15
15
 
16
16
  const thinkingBudget = typeof budgetRaw === "number" && Number.isFinite(budgetRaw) ? budgetRaw : undefined;
17
- const thinkingLevel = typeof levelRaw === "string" && levelRaw.length > 0 ? levelRaw.toLowerCase() : undefined;
17
+ const thinkingLevel =
18
+ typeof levelRaw === "string" && levelRaw.trim().length > 0 ? levelRaw.trim().toLowerCase() : undefined;
18
19
  const includeThoughts = typeof includeRaw === "boolean" ? includeRaw : undefined;
19
20
 
20
21
  if (thinkingBudget === undefined && thinkingLevel === undefined && includeThoughts === undefined) {
21
22
  return undefined;
22
23
  }
23
24
 
25
+ const thinkingEnabled =
26
+ (thinkingBudget !== undefined && thinkingBudget > 0) ||
27
+ thinkingLevel !== undefined;
28
+ const finalIncludeThoughts = thinkingEnabled ? includeThoughts ?? false : false;
29
+
24
30
  const normalized: ThinkingConfig = {};
25
31
  if (thinkingBudget !== undefined) {
26
32
  normalized.thinkingBudget = thinkingBudget;
@@ -28,9 +34,7 @@ export function normalizeThinkingConfig(config: unknown): ThinkingConfig | undef
28
34
  if (thinkingLevel !== undefined) {
29
35
  normalized.thinkingLevel = thinkingLevel;
30
36
  }
31
- if (includeThoughts !== undefined) {
32
- normalized.includeThoughts = includeThoughts;
33
- }
37
+ normalized.includeThoughts = finalIncludeThoughts;
34
38
 
35
39
  return normalized;
36
40
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
 
3
- import { enhanceGeminiErrorResponse } from "./request-helpers";
3
+ import { enhanceGeminiErrorResponse, normalizeThinkingConfig } from "./request-helpers";
4
4
 
5
5
  describe("enhanceGeminiErrorResponse", () => {
6
6
  it("adds retry hint and rate-limit message for 429 rate limits", () => {
@@ -82,3 +82,24 @@ describe("enhanceGeminiErrorResponse", () => {
82
82
  expect(result?.retryAfterMs).toBe(2000);
83
83
  });
84
84
  });
85
+
86
+ describe("normalizeThinkingConfig", () => {
87
+ it("forces includeThoughts to false when thinking is not enabled", () => {
88
+ expect(normalizeThinkingConfig({ includeThoughts: true })).toEqual({ includeThoughts: false });
89
+ expect(normalizeThinkingConfig({ thinkingBudget: 0, includeThoughts: true })).toEqual({
90
+ thinkingBudget: 0,
91
+ includeThoughts: false,
92
+ });
93
+ });
94
+
95
+ it("keeps includeThoughts when thinking is enabled by budget or level", () => {
96
+ expect(normalizeThinkingConfig({ thinkingBudget: 8192, includeThoughts: true })).toEqual({
97
+ thinkingBudget: 8192,
98
+ includeThoughts: true,
99
+ });
100
+ expect(normalizeThinkingConfig({ thinkingLevel: "HIGH", includeThoughts: true })).toEqual({
101
+ thinkingLevel: "high",
102
+ includeThoughts: true,
103
+ });
104
+ });
105
+ });
@@ -122,4 +122,80 @@ describe("request helpers", () => {
122
122
  expect(payload).toContain('"responseId":"trace-456"');
123
123
  expect(payload).not.toContain('"traceId"');
124
124
  });
125
+
126
+ it("injects model-level thinking defaults when request has no thinkingConfig", () => {
127
+ const input =
128
+ "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent";
129
+ const init: RequestInit = {
130
+ method: "POST",
131
+ headers: {
132
+ "Content-Type": "application/json",
133
+ },
134
+ body: JSON.stringify({
135
+ contents: [{ role: "user", parts: [{ text: "hi" }] }],
136
+ }),
137
+ };
138
+
139
+ const result = prepareGeminiRequest(input, init, "token-123", "project-456", {
140
+ models: {
141
+ "gemini-3-flash-preview": {
142
+ thinkingLevel: "HIGH",
143
+ includeThoughts: true,
144
+ },
145
+ },
146
+ provider: {
147
+ thinkingLevel: "low",
148
+ includeThoughts: false,
149
+ },
150
+ });
151
+
152
+ const parsed = JSON.parse(result.init.body as string) as Record<string, unknown>;
153
+ const request = parsed.request as Record<string, unknown>;
154
+ const generationConfig = request.generationConfig as Record<string, unknown>;
155
+ expect(generationConfig.thinkingConfig).toEqual({
156
+ thinkingLevel: "high",
157
+ includeThoughts: true,
158
+ });
159
+ });
160
+
161
+ it("prefers request thinkingConfig over model/provider defaults", () => {
162
+ const input =
163
+ "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent";
164
+ const init: RequestInit = {
165
+ method: "POST",
166
+ headers: {
167
+ "Content-Type": "application/json",
168
+ },
169
+ body: JSON.stringify({
170
+ contents: [{ role: "user", parts: [{ text: "hi" }] }],
171
+ generationConfig: {
172
+ thinkingConfig: {
173
+ thinkingLevel: "low",
174
+ includeThoughts: false,
175
+ },
176
+ },
177
+ }),
178
+ };
179
+
180
+ const result = prepareGeminiRequest(input, init, "token-123", "project-456", {
181
+ models: {
182
+ "gemini-3-flash-preview": {
183
+ thinkingLevel: "high",
184
+ includeThoughts: true,
185
+ },
186
+ },
187
+ provider: {
188
+ thinkingLevel: "high",
189
+ includeThoughts: true,
190
+ },
191
+ });
192
+
193
+ const parsed = JSON.parse(result.init.body as string) as Record<string, unknown>;
194
+ const request = parsed.request as Record<string, unknown>;
195
+ const generationConfig = request.generationConfig as Record<string, unknown>;
196
+ expect(generationConfig.thinkingConfig).toEqual({
197
+ thinkingLevel: "low",
198
+ includeThoughts: false,
199
+ });
200
+ });
125
201
  });
package/src/plugin.ts CHANGED
@@ -12,6 +12,7 @@ import { maybeShowGeminiCapacityToast, maybeShowGeminiTestToast } from "./plugin
12
12
  import {
13
13
  isGenerativeLanguageRequest,
14
14
  prepareGeminiRequest,
15
+ type ThinkingConfigDefaults,
15
16
  transformGeminiResponse,
16
17
  } from "./plugin/request";
17
18
  import { fetchWithRetry } from "./plugin/retry";
@@ -68,6 +69,7 @@ export const GeminiCLIOAuthPlugin = async (
68
69
  const configuredProjectId = resolveConfiguredProjectId(provider);
69
70
  latestGeminiConfiguredProjectId = configuredProjectId;
70
71
  normalizeProviderModelCosts(provider);
72
+ const thinkingConfigDefaults = resolveThinkingConfigDefaults(provider);
71
73
 
72
74
  return {
73
75
  apiKey: "",
@@ -109,6 +111,7 @@ export const GeminiCLIOAuthPlugin = async (
109
111
  init,
110
112
  authRecord.access,
111
113
  projectContext.effectiveProjectId,
114
+ thinkingConfigDefaults,
112
115
  );
113
116
  const debugContext = startGeminiDebugRequest({
114
117
  originalUrl: toUrlString(input),
@@ -187,6 +190,34 @@ function normalizeProviderModelCosts(provider: Provider): void {
187
190
  }
188
191
  }
189
192
 
193
+ function resolveThinkingConfigDefaults(provider: Provider): ThinkingConfigDefaults | undefined {
194
+ const providerOptions =
195
+ provider && typeof provider === "object"
196
+ ? ((provider as { options?: Record<string, unknown> }).options ?? undefined)
197
+ : undefined;
198
+ const providerThinkingConfig = providerOptions?.thinkingConfig;
199
+
200
+ const modelThinkingConfigByModel: Record<string, unknown> = {};
201
+ for (const [modelId, model] of Object.entries(provider.models ?? {})) {
202
+ if (!model || typeof model !== "object") {
203
+ continue;
204
+ }
205
+ const modelOptions = (model as { options?: Record<string, unknown> }).options;
206
+ if (modelOptions && typeof modelOptions === "object" && "thinkingConfig" in modelOptions) {
207
+ modelThinkingConfigByModel[modelId] = modelOptions.thinkingConfig;
208
+ }
209
+ }
210
+
211
+ if (providerThinkingConfig === undefined && Object.keys(modelThinkingConfigByModel).length === 0) {
212
+ return undefined;
213
+ }
214
+
215
+ return {
216
+ provider: providerThinkingConfig,
217
+ models: modelThinkingConfigByModel,
218
+ };
219
+ }
220
+
190
221
  async function ensureProjectContextOrThrow(
191
222
  authRecord: OAuthAuthDetails,
192
223
  client: PluginClient,