Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

### Fixed

- 默认不再发送 `reasoning.effort`:移除 `modelInfo.defaultReasoningEffort` 自动兜底,`default_reasoning_effort` 默认改为 `null`,彻底消除简单对话触发 medium 推理导致的 token 暴涨;Dashboard 新增 "Disabled (no reasoning)" 选项,用户可按需开启

- 上游 401 时立即触发 RT→AT 刷新,而非等待定时器(修复 token 被提前作废后账号一直显示 expired 的问题)
- Dashboard session 滑动窗口续期:每次有效请求自动延长过期时间,不再固定 TTL 后断连
- Dashboard 前端全局 401 拦截:session 过期后自动跳回登录页,不再卡死在空白页
Expand Down
2 changes: 1 addition & 1 deletion config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ client:
chromium_version: "144"
model:
default: gpt-5.2-codex
default_reasoning_effort: medium
default_reasoning_effort: null
default_service_tier: null
inject_desktop_context: false
suppress_desktop_directives: false
Expand Down
2 changes: 1 addition & 1 deletion shared/hooks/use-general-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface GeneralSettingsData {
inject_desktop_context: boolean;
suppress_desktop_directives: boolean;
default_model: string;
default_reasoning_effort: string;
default_reasoning_effort: string | null;
refresh_enabled: boolean;
refresh_margin_seconds: number;
refresh_concurrency: number;
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/config-local-override.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ client:
chromium_version: "136"
model:
default: test-model
default_reasoning_effort: medium
default_reasoning_effort: null
default_service_tier: null
inject_desktop_context: false
suppress_desktop_directives: false
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/config-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe("ConfigSchema", () => {
expect(result.auth.max_concurrent_per_account).toBe(3);
expect(result.auth.request_interval_ms).toBe(50);
expect(result.model.default).toBe("gpt-5.2-codex");
expect(result.model.default_reasoning_effort).toBe("medium");
expect(result.model.default_reasoning_effort).toBeNull();
expect(result.tls.force_http11).toBe(false);
expect(result.quota.refresh_interval_minutes).toBe(5);
expect(result.quota.warning_thresholds.primary).toEqual([80, 90]);
Expand Down
2 changes: 1 addition & 1 deletion src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const ConfigSchema = z.object({
}),
model: z.object({
default: z.string().default("gpt-5.2-codex"),
default_reasoning_effort: z.string().default("medium"),
default_reasoning_effort: z.string().nullable().default(null),
default_service_tier: z.string().nullable().default(null),
inject_desktop_context: z.boolean().default(false),
suppress_desktop_directives: z.boolean().default(true),
Expand Down
28 changes: 26 additions & 2 deletions src/routes/__tests__/general-settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
const mockConfig = {
server: { port: 8080, proxy_api_key: null as string | null },
tls: { proxy_url: null as string | null, force_http11: false },
model: { default: "gpt-5.2-codex", default_reasoning_effort: "medium", inject_desktop_context: false, suppress_desktop_directives: true },
model: { default: "gpt-5.2-codex", default_reasoning_effort: null as string | null, inject_desktop_context: false, suppress_desktop_directives: true },
quota: {
refresh_interval_minutes: 5,
warning_thresholds: { primary: [80, 90], secondary: [80, 90] },
Expand Down Expand Up @@ -114,7 +114,7 @@ describe("GET /admin/general-settings", () => {
inject_desktop_context: false,
suppress_desktop_directives: true,
default_model: "gpt-5.2-codex",
default_reasoning_effort: "medium",
default_reasoning_effort: null,
refresh_enabled: true,
refresh_margin_seconds: 300,
refresh_concurrency: 2,
Expand Down Expand Up @@ -325,6 +325,30 @@ describe("POST /admin/general-settings", () => {
expect(res.status).toBe(400);
});

it("accepts null default_reasoning_effort to disable reasoning", async () => {
mockConfig.model.default_reasoning_effort = "medium";
const app = makeApp();
const res = await app.request("/admin/general-settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ default_reasoning_effort: null }),
});
expect(res.status).toBe(200);
const data = await res.json();
expect(data.success).toBe(true);
expect(mutateYaml).toHaveBeenCalledOnce();
});

it("rejects invalid default_reasoning_effort", async () => {
const app = makeApp();
const res = await app.request("/admin/general-settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ default_reasoning_effort: "ultra" }),
});
expect(res.status).toBe(400);
});

it("requires auth when proxy_api_key is set", async () => {
mockConfig.server.proxy_api_key = "my-secret";
const app = makeApp();
Expand Down
9 changes: 6 additions & 3 deletions src/routes/admin/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function createSettingsRoutes(): Hono {
inject_desktop_context?: boolean;
suppress_desktop_directives?: boolean;
default_model?: string;
default_reasoning_effort?: string;
default_reasoning_effort?: string | null;
refresh_enabled?: boolean;
refresh_margin_seconds?: number;
refresh_concurrency?: number;
Expand Down Expand Up @@ -161,9 +161,12 @@ export function createSettingsRoutes(): Hono {

if (body.default_reasoning_effort !== undefined) {
const validEfforts = ["low", "medium", "high", "xhigh"];
if (!validEfforts.includes(body.default_reasoning_effort)) {
if (
body.default_reasoning_effort !== null &&
!validEfforts.includes(body.default_reasoning_effort)
) {
c.status(400);
return c.json({ error: `default_reasoning_effort must be one of: ${validEfforts.join(", ")}` });
return c.json({ error: `default_reasoning_effort must be one of: ${validEfforts.join(", ")} or null` });
}
}

Expand Down
16 changes: 9 additions & 7 deletions src/routes/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,19 +486,21 @@ export function createResponsesRoutes(
codexRequest.previous_response_id = body.previous_response_id;
}

// Reasoning effort: explicit body > suffix > model default > config default
// Reasoning effort: explicit body > suffix > config default
const effort =
(isRecord(body.reasoning) && typeof body.reasoning.effort === "string"
? body.reasoning.effort
: null) ??
parsed.reasoningEffort ??
modelInfo?.defaultReasoningEffort ??
config.model.default_reasoning_effort;
const summary =
isRecord(body.reasoning) && typeof body.reasoning.summary === "string"
? body.reasoning.summary
: "auto";
codexRequest.reasoning = { summary, ...(effort ? { effort } : {}) };
const clientReasoningRecord = isRecord(body.reasoning) ? body.reasoning : null;
if (effort || clientReasoningRecord) {
const summary =
clientReasoningRecord && typeof clientReasoningRecord.summary === "string"
? clientReasoningRecord.summary
: "auto";
codexRequest.reasoning = { summary, ...(effort ? { effort } : {}) };
}

// Service tier
const serviceTier =
Expand Down
42 changes: 38 additions & 4 deletions src/routes/shared/proxy-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,24 @@ export async function handleProxyRequest(
const inputItems = req.codexRequest.input?.length ?? 0;
const instrLen = req.codexRequest.instructions?.length ?? 0;
const affinityHit = preferredEntryId && entryId === preferredEntryId;
const reasoningField = req.codexRequest.reasoning
? `effort=${req.codexRequest.reasoning.effort ?? "none"} summary=${req.codexRequest.reasoning.summary ?? "none"}`
: "off";
console.log(
`[${fmt.tag}] Account ${entryId} | model=${req.model} | input_items=${inputItems} instr=${instrLen}B payload=${reqJson.length}B` +
`[${fmt.tag}] Account ${entryId} | model=${req.model} | input_items=${inputItems} instr=${instrLen}B payload=${reqJson.length}B reasoning=[${reasoningField}]` +
(prevRespId ? ` | affinity=${affinityHit ? "hit" : "miss"}` : ""),
);
if (reqJson.length > 50_000) {
// Log per-item size breakdown to diagnose large payload origin
const itemSizes = (req.codexRequest.input ?? []).map((item, i) => {
const sz = JSON.stringify(item).length;
const role = typeof item === "object" && item !== null && "role" in item ? (item as Record<string, unknown>).role : (item as Record<string, unknown>).type;
return ` [${i}] ${role} ${sz}B`;
});
console.warn(
`[${fmt.tag}] ⚠ Large payload (${(reqJson.length / 1024).toFixed(1)}KB) — input_items=${inputItems} instr=${instrLen}B`,
`[${fmt.tag}] ⚠ Large payload (${(reqJson.length / 1024).toFixed(1)}KB) — input_items=${inputItems} instr=${instrLen}B\n` +
` instructions: ${instrLen}B\n` +
itemSizes.join("\n"),
);
}
}
Expand Down Expand Up @@ -208,11 +219,21 @@ export async function handleProxyRequest(
affinityMap.record(capturedResponseId, capturedEntryId, conversationId, upstreamTurnState);
}
if (usageInfo) {
const uncached = usageInfo.cached_tokens
? usageInfo.input_tokens - usageInfo.cached_tokens
: usageInfo.input_tokens;
console.log(
`[${fmt.tag}] Account ${capturedEntryId} | Usage: in=${usageInfo.input_tokens} out=${usageInfo.output_tokens}` +
(usageInfo.cached_tokens ? ` cached=${usageInfo.cached_tokens}` : "") +
`[${fmt.tag}] Account ${capturedEntryId} | Usage: in=${usageInfo.input_tokens}` +
(usageInfo.cached_tokens ? ` (cached=${usageInfo.cached_tokens} uncached=${uncached})` : "") +
` out=${usageInfo.output_tokens}` +
(usageInfo.reasoning_tokens ? ` reasoning=${usageInfo.reasoning_tokens}` : ""),
);
if (usageInfo.input_tokens > 10_000) {
console.warn(
`[${fmt.tag}] ⚠ High input token count: ${usageInfo.input_tokens} tokens` +
(usageInfo.reasoning_tokens ? ` (reasoning=${usageInfo.reasoning_tokens})` : ""),
);
}
}
releaseAccount(accountPool, capturedEntryId, usageInfo, released);
}
Expand Down Expand Up @@ -295,6 +316,19 @@ async function handleNonStreaming(
if (result.responseId && affinityMap && conversationId) {
affinityMap.record(result.responseId, currentEntryId, conversationId, turnState);
}
if (result.usage) {
const u = result.usage;
const uncached = u.cached_tokens ? u.input_tokens - u.cached_tokens : u.input_tokens;
console.log(
`[${fmt.tag}] Account ${currentEntryId} | Usage: in=${u.input_tokens}` +
(u.cached_tokens ? ` (cached=${u.cached_tokens} uncached=${uncached})` : "") +
` out=${u.output_tokens}` +
(u.reasoning_tokens ? ` reasoning=${u.reasoning_tokens}` : ""),
);
if (u.input_tokens > 10_000) {
console.warn(`[${fmt.tag}] ⚠ High input token count: ${u.input_tokens} tokens`);
}
}
releaseAccount(accountPool, currentEntryId, result.usage, released);
return c.json(result.response);
} catch (collectErr) {
Expand Down
7 changes: 4 additions & 3 deletions src/translation/anthropic-to-codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,15 @@ export function translateAnthropicToCodexRequest(
request.tool_choice = codexToolChoice;
}

// Reasoning effort: thinking config > suffix > model default > config default
// Reasoning effort: thinking config > suffix > config default
const thinkingEffort = mapThinkingToEffort(req.thinking);
const effort =
thinkingEffort ??
parsed.reasoningEffort ??
modelInfo?.defaultReasoningEffort ??
cfg.default_reasoning_effort;
request.reasoning = { summary: "auto", ...(effort ? { effort } : {}) };
if (effort) {
request.reasoning = { effort, summary: "auto" };
}

// Service tier: suffix > config default
const serviceTier =
Expand Down
7 changes: 4 additions & 3 deletions src/translation/gemini-to-codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,17 @@ export function translateGeminiToCodexRequest(
request.tool_choice = codexToolChoice;
}

// Reasoning effort: thinking config > suffix > model default > config default
// Reasoning effort: thinking config > suffix > config default
const thinkingEffort = budgetToEffort(
req.generationConfig?.thinkingConfig?.thinkingBudget,
);
const effort =
thinkingEffort ??
parsed.reasoningEffort ??
modelInfo?.defaultReasoningEffort ??
cfg.default_reasoning_effort;
request.reasoning = { summary: "auto", ...(effort ? { effort } : {}) };
if (effort) {
request.reasoning = { effort, summary: "auto" };
}

// Service tier: suffix > config default
const serviceTier =
Expand Down
7 changes: 4 additions & 3 deletions src/translation/openai-to-codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,14 @@ export function translateToCodexRequest(
request.tool_choice = codexToolChoice;
}

// Reasoning effort: explicit API field > suffix > model default > config default
// Reasoning effort: explicit API field > suffix > config default
const effort =
req.reasoning_effort ??
parsed.reasoningEffort ??
modelInfo?.defaultReasoningEffort ??
cfg.default_reasoning_effort;
request.reasoning = { summary: "auto", ...(effort ? { effort } : {}) };
if (effort) {
request.reasoning = { effort, summary: "auto" };
}

// Service tier: explicit API field > suffix > config default
const serviceTier =
Expand Down
6 changes: 3 additions & 3 deletions tests/integration/account-routing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ vi.mock("@src/config.js", () => ({
},
model: {
default: "gpt-5.2-codex",
default_reasoning_effort: "medium",
default_reasoning_effort: null,
default_service_tier: null,
},
server: { proxy_api_key: null },
Expand Down Expand Up @@ -73,7 +73,7 @@ describe("account-routing integration", () => {
},
model: {
default: "gpt-5.2-codex",
default_reasoning_effort: "medium",
default_reasoning_effort: null,
default_service_tier: null,
},
server: { proxy_api_key: null },
Expand Down Expand Up @@ -166,7 +166,7 @@ describe("account-routing integration", () => {
},
model: {
default: "gpt-5.2-codex",
default_reasoning_effort: "medium",
default_reasoning_effort: null,
default_service_tier: null,
},
server: { proxy_api_key: null },
Expand Down
Loading
Loading