Skip to content

Commit e1e2165

Browse files
committed
refactor(request): resolve default thinking config
1 parent ac2e2b8 commit e1e2165

7 files changed

Lines changed: 183 additions & 11 deletions

File tree

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "opencode-gemini-auth",
33
"module": "index.ts",
4-
"version": "1.4.5",
4+
"version": "1.4.6",
55
"author": "jenslys",
66
"repository": "https://github.com/jenslys/opencode-gemini-auth",
77
"files": [

‎src/plugin.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { maybeShowGeminiCapacityToast, maybeShowGeminiTestToast } from "./plugin
1212
import {
1313
isGenerativeLanguageRequest,
1414
prepareGeminiRequest,
15+
type ThinkingConfigDefaults,
1516
transformGeminiResponse,
1617
} from "./plugin/request";
1718
import { fetchWithRetry } from "./plugin/retry";
@@ -68,6 +69,7 @@ export const GeminiCLIOAuthPlugin = async (
6869
const configuredProjectId = resolveConfiguredProjectId(provider);
6970
latestGeminiConfiguredProjectId = configuredProjectId;
7071
normalizeProviderModelCosts(provider);
72+
const thinkingConfigDefaults = resolveThinkingConfigDefaults(provider);
7173

7274
return {
7375
apiKey: "",
@@ -109,6 +111,7 @@ export const GeminiCLIOAuthPlugin = async (
109111
init,
110112
authRecord.access,
111113
projectContext.effectiveProjectId,
114+
thinkingConfigDefaults,
112115
);
113116
const debugContext = startGeminiDebugRequest({
114117
originalUrl: toUrlString(input),
@@ -187,6 +190,34 @@ function normalizeProviderModelCosts(provider: Provider): void {
187190
}
188191
}
189192

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+
190221
async function ensureProjectContextOrThrow(
191222
authRecord: OAuthAuthDetails,
192223
client: PluginClient,

‎src/plugin/request-helpers.test.ts‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from "bun:test";
22

3-
import { enhanceGeminiErrorResponse } from "./request-helpers";
3+
import { enhanceGeminiErrorResponse, normalizeThinkingConfig } from "./request-helpers";
44

55
describe("enhanceGeminiErrorResponse", () => {
66
it("adds retry hint and rate-limit message for 429 rate limits", () => {
@@ -82,3 +82,24 @@ describe("enhanceGeminiErrorResponse", () => {
8282
expect(result?.retryAfterMs).toBe(2000);
8383
});
8484
});
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+
});

‎src/plugin/request-helpers/thinking.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,27 @@ export function normalizeThinkingConfig(config: unknown): ThinkingConfig | undef
1414
const includeRaw = record.includeThoughts ?? record.include_thoughts;
1515

1616
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;
1819
const includeThoughts = typeof includeRaw === "boolean" ? includeRaw : undefined;
1920

2021
if (thinkingBudget === undefined && thinkingLevel === undefined && includeThoughts === undefined) {
2122
return undefined;
2223
}
2324

25+
const thinkingEnabled =
26+
(thinkingBudget !== undefined && thinkingBudget > 0) ||
27+
thinkingLevel !== undefined;
28+
const finalIncludeThoughts = thinkingEnabled ? includeThoughts ?? false : false;
29+
2430
const normalized: ThinkingConfig = {};
2531
if (thinkingBudget !== undefined) {
2632
normalized.thinkingBudget = thinkingBudget;
2733
}
2834
if (thinkingLevel !== undefined) {
2935
normalized.thinkingLevel = thinkingLevel;
3036
}
31-
if (includeThoughts !== undefined) {
32-
normalized.includeThoughts = includeThoughts;
33-
}
37+
normalized.includeThoughts = finalIncludeThoughts;
3438

3539
return normalized;
3640
}

‎src/plugin/request.test.ts‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,80 @@ describe("request helpers", () => {
122122
expect(payload).toContain('"responseId":"trace-456"');
123123
expect(payload).not.toContain('"traceId"');
124124
});
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+
});
125201
});

‎src/plugin/request/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { prepareGeminiRequest } from "./prepare";
2+
export type { ThinkingConfigDefaults } from "./prepare";
23
export { transformGeminiResponse } from "./response";
34
export { isGenerativeLanguageRequest } from "./shared";

‎src/plugin/request/prepare.ts‎

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ const MODEL_FALLBACKS: Record<string, string> = {
1212
"gemini-2.5-flash-image": "gemini-2.5-flash",
1313
};
1414

15+
export interface ThinkingConfigDefaults {
16+
provider?: unknown;
17+
models?: Record<string, unknown>;
18+
}
19+
1520
/**
1621
* Rewrites OpenAI-style requests into Gemini Code Assist request shape.
1722
*/
@@ -20,6 +25,7 @@ export function prepareGeminiRequest(
2025
init: RequestInit | undefined,
2126
accessToken: string,
2227
projectId: string,
28+
thinkingConfigDefaults?: ThinkingConfigDefaults,
2329
): {
2430
request: RequestInfo;
2531
init: RequestInit;
@@ -61,7 +67,13 @@ export function prepareGeminiRequest(
6167
let requestIdentifier: string = randomUUID();
6268

6369
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+
);
6577
if (transformed.body) {
6678
body = transformed.body;
6779
requestIdentifier = transformed.userPromptId;
@@ -97,6 +109,8 @@ function transformRequestBody(
97109
body: string,
98110
projectId: string,
99111
effectiveModel: string,
112+
requestedModel: string,
113+
thinkingConfigDefaults?: ThinkingConfigDefaults,
100114
): { body?: string; userPromptId: string } {
101115
const fallbackId = randomUUID();
102116
try {
@@ -115,7 +129,11 @@ function transformRequestBody(
115129
const requestPayload = { ...parsedBody };
116130
transformOpenAIToolCalls(requestPayload);
117131
addThoughtSignaturesToFunctionCalls(requestPayload);
118-
normalizeThinking(requestPayload);
132+
normalizeThinking(
133+
requestPayload,
134+
resolveDefaultThinkingConfig(thinkingConfigDefaults, requestedModel, effectiveModel),
135+
thinkingConfigDefaults?.provider,
136+
);
119137
normalizeSystemInstruction(requestPayload);
120138
normalizeCachedContent(requestPayload);
121139
stripThoughtPartsFromHistory(requestPayload);
@@ -139,9 +157,30 @@ function transformRequestBody(
139157
}
140158
}
141159

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 {
143177
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);
145184
if (normalizedThinking) {
146185
if (rawGenerationConfig) {
147186
rawGenerationConfig.thinkingConfig = normalizedThinking;
@@ -152,7 +191,7 @@ function normalizeThinking(requestPayload: Record<string, unknown>): void {
152191
return;
153192
}
154193

155-
if (rawGenerationConfig?.thinkingConfig) {
194+
if (hasRequestThinkingConfig && rawGenerationConfig) {
156195
delete rawGenerationConfig.thinkingConfig;
157196
requestPayload.generationConfig = rawGenerationConfig;
158197
}

0 commit comments

Comments
 (0)