diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
index 672961cd90..4164626ec6 100644
--- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue
+++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
@@ -56,6 +56,8 @@ import {
useSettingsStore,
AI_PROVIDER_PRESETS,
AI_PROVIDER_PARTNER_PRESETS,
+ AI_OUTPUT_TOKENS_MAX,
+ AI_OUTPUT_TOKENS_MIN,
EDITOR_THEMES,
DEFAULT_EDITOR_SETTINGS,
DEFAULT_DESKTOP_SETTINGS,
@@ -3524,6 +3526,7 @@ const aiEditProxyEnabled = ref(false);
const aiEditProxyUrl = ref("");
const aiEditEnableThinking = ref(true);
const aiEditReasoningLevel = ref("default");
+const aiEditMaxOutputTokens = ref(undefined);
const aiEditContextWindow = ref(undefined);
const aiEditCodexCliPath = ref("");
const aiEditCodexCliEnvRows = ref([]);
@@ -3808,6 +3811,7 @@ function currentAiEditConfig() {
proxyUrl: aiEditProxyUrl.value,
enableThinking: aiEditEnableThinking.value,
reasoningLevel: aiEditReasoningLevel.value,
+ maxOutputTokens: aiEditMaxOutputTokens.value || undefined,
contextWindow: aiEditContextWindow.value || undefined,
codexCliPath: aiEditCodexCliPath.value.trim() || undefined,
codexCliEnv: aiIsCodexCli.value ? cliEnvFromRows(aiEditCodexCliEnvRows.value) : {},
@@ -3894,6 +3898,7 @@ function aiEnterEditMode(configId?: string) {
aiEditProxyUrl.value = config.proxyUrl ?? "";
aiEditEnableThinking.value = config.enableThinking ?? true;
aiEditReasoningLevel.value = config.reasoningLevel ?? "default";
+ aiEditMaxOutputTokens.value = config.maxOutputTokens;
aiEditContextWindow.value = config.contextWindow;
aiEditCodexCliPath.value = config.codexCliPath ?? "";
aiEditCodexCliEnvRows.value = aiEnvRowsFromConfig(config.codexCliEnv);
@@ -3927,6 +3932,7 @@ function aiEnterEditMode(configId?: string) {
aiEditProxyUrl.value = "";
aiEditEnableThinking.value = true;
aiEditReasoningLevel.value = "default";
+ aiEditMaxOutputTokens.value = undefined;
aiEditContextWindow.value = undefined;
aiEditCodexCliPath.value = "";
aiEditCodexCliEnvRows.value = [];
@@ -7735,6 +7741,16 @@ onUnmounted(() => {
+
+
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 3f86e9ecdc..2eba94392d 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -2777,6 +2777,9 @@ export default {
contextWindow: "Context Window",
contextWindowAuto: "Auto (detect from model name)",
contextWindowHint: "Tokens. Leave empty to auto-detect. Set manually for local/custom models.",
+ maxOutputTokens: "Max Output Tokens",
+ maxOutputTokensAuto: "Auto (per action)",
+ maxOutputTokensHint: "Optional per-configuration limit sent to the provider. Range: {min}–{max}. Thinking requests still use at least 8192 tokens.",
maxRetries: "Max Retries",
maxRetriesHint: "Retry on rate limits, timeouts, and temporary network errors. 0 = never, max 10.",
maxRetriesGlobal: "Max Retries",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index b252c8cbff..344ae6da86 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -2847,6 +2847,9 @@ export default withEnglishFallback({
contextWindow: "Ventana de contexto",
contextWindowAuto: "Auto (detectar desde el nombre del modelo)",
contextWindowHint: "Tokens. Déjalo vacío para detección automática. Configúralo manualmente para modelos locales/personalizados.",
+ maxOutputTokens: "Tokens máximos de salida",
+ maxOutputTokensAuto: "Auto (por acción)",
+ maxOutputTokensHint: "Límite opcional por configuración enviado al proveedor. Rango: {min}–{max}. Las solicitudes con razonamiento usan al menos 8192 tokens.",
maxRetriesGlobal: "Reintentos máximos",
maxRetriesGlobalDescription: "Número de reintentos automáticos en errores transitorios de API (límite de tasa, timeout, fallos de red). Se aplica a todos los proveedores de IA por API. Los proveedores CLI no se ven afectados.",
maxRetriesRange: "{min}–{max}, predeterminado {default}",
diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts
index bb8a7b1114..568e27836f 100644
--- a/apps/desktop/src/i18n/locales/it.ts
+++ b/apps/desktop/src/i18n/locales/it.ts
@@ -2741,6 +2741,9 @@ export default withEnglishFallback({
contextWindow: "Finestra di Contesto",
contextWindowAuto: "Auto (rileva dal nome del modello)",
contextWindowHint: "Token. Lascia vuoto per il rilevamento automatico. Imposta manualmente per modelli locali/personalizzati.",
+ maxOutputTokens: "Token di output massimi",
+ maxOutputTokensAuto: "Automatico (per azione)",
+ maxOutputTokensHint: "Limite opzionale per configurazione inviato al provider. Intervallo: {min}–{max}. Le richieste con ragionamento usano comunque almeno 8192 token.",
maxRetriesGlobal: "Tentativi massimi",
maxRetriesGlobalDescription: "Numero di tentativi automatici su errori API transitori (limite di velocità, timeout, problemi di rete). Si applica a tutti i provider IA via API. I provider CLI non sono interessati.",
maxRetriesRange: "{min}–{max}, predefinito {default}",
diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts
index 829a9f20fa..9bab198ef8 100644
--- a/apps/desktop/src/i18n/locales/ja.ts
+++ b/apps/desktop/src/i18n/locales/ja.ts
@@ -2541,6 +2541,9 @@ export default withEnglishFallback({
contextWindow: "コンテキストウィンドウ",
contextWindowAuto: "自動(モデル名から検出)",
contextWindowHint: "トークン数。空欄で自動検出。ローカル/カスタムモデルは手動設定してください。",
+ maxOutputTokens: "最大出力トークン",
+ maxOutputTokensAuto: "自動(アクションごと)",
+ maxOutputTokensHint: "プロバイダーに送信する設定ごとの任意の出力上限。範囲: {min}–{max}。思考を有効にしたリクエストでは少なくとも8192トークンを使用します。",
maxRetriesGlobal: "最大再試行回数",
maxRetriesGlobalDescription: "一時的なAPIエラー(レート制限、タイムアウト、ネットワーク障害)発生時の自動再試行回数。すべてのAPI対応AIプロバイダーに適用されます。CLIプロバイダーは影響を受けません。",
maxRetriesRange: "{min}–{max}、デフォルト {default}",
diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts
index f15a541435..7f645551cb 100644
--- a/apps/desktop/src/i18n/locales/ko.ts
+++ b/apps/desktop/src/i18n/locales/ko.ts
@@ -2592,6 +2592,9 @@ export default withEnglishFallback({
contextWindow: "컨텍스트 창",
contextWindowAuto: "자동 (모델 이름에서 감지)",
contextWindowHint: "토큰. 자동 감지하려면 비워 두세요. 로컬/사용자 지정 모델은 수동으로 설정하세요.",
+ maxOutputTokens: "최대 출력 토큰",
+ maxOutputTokensAuto: "자동 (모델 기본값)",
+ maxOutputTokensHint: "공급자에게 전송되는 선택적 구성별 출력 토큰 제한입니다. 범위: {min}–{max}. 사고 요청은 여전히 최소 8192 토큰을 사용합니다.",
codexCliPath: "Codex CLI 경로",
codexCliPathHint: "PATH의 codex를 사용하려면 비워 두세요. codex login으로 별도로 로그인하세요.",
codexCliPathEnvError: "Codex CLI 경로에는 실행 파일 경로만 포함해야 합니다. 환경 변수는 아래에 추가하세요.",
diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts
index f9dbbd6e51..29c6a696c7 100644
--- a/apps/desktop/src/i18n/locales/pt-BR.ts
+++ b/apps/desktop/src/i18n/locales/pt-BR.ts
@@ -2845,6 +2845,9 @@ export default withEnglishFallback({
contextWindow: "Janela de Contexto",
contextWindowAuto: "Automático (detectar pelo nome do modelo)",
contextWindowHint: "Tokens. Deixe vazio para detecção automática. Defina manualmente para modelos locais/personalizados.",
+ maxOutputTokens: "Máximo de tokens de saída",
+ maxOutputTokensAuto: "Automático (por ação)",
+ maxOutputTokensHint: "Limite opcional por configuração enviado ao provedor. Intervalo: {min}–{max}. Solicitações com raciocínio ainda usam pelo menos 8192 tokens.",
maxRetriesGlobal: "Máximo de Tentativas",
maxRetriesGlobalDescription: "Número de tentativas automáticas em erros transitórios de API (limite de taxa, timeout, falhas de rede). Aplica-se a todos os provedores de IA via API. Provedores CLI não são afetados.",
maxRetriesRange: "{min}–{max}, padrão {default}",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index d19a1724fa..48bb0ea6f2 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -2700,6 +2700,9 @@ export default withEnglishFallback({
contextWindow: "上下文窗口",
contextWindowAuto: "自动(根据模型名称推断)",
contextWindowHint: "单位:token。留空自动推断,本地/自定义模型建议手动设置。",
+ maxOutputTokens: "最大输出 Token 数",
+ maxOutputTokensAuto: "自动(按操作类型)",
+ maxOutputTokensHint: "可选的单配置输出上限,会发送给 AI 服务商。范围:{min}–{max}。启用思考时仍至少使用 8192 token。",
maxRetries: "最大重试次数",
maxRetriesHint: "遇到限流、超时或临时网络错误时自动重试。0 = 不重试,最大 10。",
maxRetriesGlobal: "最大重试次数",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index dac157ab8f..e016d82c6c 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -2740,6 +2740,9 @@ export default withEnglishFallback({
contextWindow: "Context Window",
contextWindowAuto: "自動(從模型名稱偵測)",
contextWindowHint: "Token 數。留空以自動偵測。本機/自訂模型請手動設定。",
+ maxOutputTokens: "最大輸出 Token 數",
+ maxOutputTokensAuto: "自動(依操作類型)",
+ maxOutputTokensHint: "可選的單一設定輸出上限,會傳送給 AI 服務商。範圍:{min}–{max}。啟用思考時仍至少使用 8192 token。",
maxRetries: "最大重試次數",
maxRetriesHint: "遇到限流、超時或暫時性網路錯誤時自動重試。0 = 不重試,最大 10。",
maxRetriesGlobal: "最大重試次數",
diff --git a/apps/desktop/src/lib/__tests__/ai/ai.spec.ts b/apps/desktop/src/lib/__tests__/ai/ai.spec.ts
index a4162fbff8..c53d3fbf50 100644
--- a/apps/desktop/src/lib/__tests__/ai/ai.spec.ts
+++ b/apps/desktop/src/lib/__tests__/ai/ai.spec.ts
@@ -158,6 +158,44 @@ describe("AI SQL dialect prompt", () => {
expect(prompt).toContain("Ask mode");
});
+ it("uses the configured output budget for non-thinking requests", () => {
+ const request = buildAgentRequest({
+ config: {
+ provider: "deepseek",
+ apiKey: "test",
+ apiUrl: "https://example.invalid",
+ model: "deepseek-v4-flash",
+ enableThinking: false,
+ maxOutputTokens: 1024,
+ },
+ action: "general",
+ mode: "ask",
+ instruction: "generate a long migration",
+ context: context(),
+ });
+
+ expect(request.maxTokens).toBe(1024);
+ });
+
+ it("uses the configured output budget above the thinking minimum", () => {
+ const request = buildAgentRequest({
+ config: {
+ provider: "deepseek",
+ apiKey: "test",
+ apiUrl: "https://example.invalid",
+ model: "deepseek-v4-flash",
+ enableThinking: true,
+ maxOutputTokens: 32768,
+ },
+ action: "general",
+ mode: "ask",
+ instruction: "generate a long migration",
+ context: context(),
+ });
+
+ expect(request.maxTokens).toBe(32768);
+ });
+
it("MongoDB agent mode uses shell commands instead of SQL", () => {
const prompt = buildSystemPrompt("general", context({ databaseType: "mongodb", connectionName: "MongoDB", database: "benchmark" }), "agent");
diff --git a/apps/desktop/src/lib/ai/ai.ts b/apps/desktop/src/lib/ai/ai.ts
index 3fb4b741bd..00713f3955 100644
--- a/apps/desktop/src/lib/ai/ai.ts
+++ b/apps/desktop/src/lib/ai/ai.ts
@@ -179,7 +179,8 @@ export function buildAgentRequest(input: AiRequestInput, history?: api.AiMessage
];
const params = actionParams(input.action);
- const maxTokens = input.config.enableThinking ? Math.max(params.maxTokens, 8192) : params.maxTokens;
+ const baseMaxTokens = input.config.maxOutputTokens ?? params.maxTokens;
+ const maxTokens = input.config.enableThinking ? Math.max(baseMaxTokens, 8192) : baseMaxTokens;
return { messages, systemPrompt, taskContract, maxTokens };
}
diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts
index 57ceddd4d3..9239ac3708 100644
--- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts
+++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts
@@ -669,6 +669,12 @@ describe("settingsStore AI API key normalization", () => {
it("trims API keys when normalizing loaded configurations", () => {
expect(normalizeAiConfig({ provider: "openai", apiKey: " secret " }).apiKey).toBe("secret");
});
+ it("preserves and bounds a configured maximum output token budget", () => {
+ expect(normalizeAiConfig({ provider: "deepseek", maxOutputTokens: 32768 }).maxOutputTokens).toBe(32768);
+ expect(normalizeAiConfig({ provider: "deepseek", maxOutputTokens: 1_500_000 }).maxOutputTokens).toBe(1_000_000);
+ expect(normalizeAiConfig({ provider: "deepseek", maxOutputTokens: 128 }).maxOutputTokens).toBeUndefined();
+ expect(normalizeAiConfig({ provider: "deepseek", maxOutputTokens: Number.NaN }).maxOutputTokens).toBeUndefined();
+ });
it("provides Kimi defaults and recognizes legacy Kimi configurations", () => {
expect(AI_PROVIDER_PRESETS.kimi).toMatchObject({
diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts
index 8341127187..ff7da26cc2 100644
--- a/apps/desktop/src/stores/settingsStore.ts
+++ b/apps/desktop/src/stores/settingsStore.ts
@@ -458,6 +458,8 @@ const defaultConfigs: Record
> = Object.from
) as Record>;
const AI_REASONING_LEVELS: AiReasoningLevel[] = ["default", "minimal", "low", "medium", "high", "xhigh", "max"];
+export const AI_OUTPUT_TOKENS_MIN = 256;
+export const AI_OUTPUT_TOKENS_MAX = 1_000_000;
const AI_ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
function normalizeAiReasoningLevel(value: unknown): AiReasoningLevel {
@@ -488,6 +490,8 @@ export function normalizeAiHeaders(value: unknown): Record {
export function normalizeAiConfig(config: Partial | null | undefined): AiConfig {
const provider = config?.provider && config.provider in AI_PROVIDER_PRESETS ? config.provider : inferAiProviderFromConfig(config);
+ const rawMaxOutputTokens = config?.maxOutputTokens;
+ const maxOutputTokens = typeof rawMaxOutputTokens === "number" && Number.isFinite(rawMaxOutputTokens) && rawMaxOutputTokens >= AI_OUTPUT_TOKENS_MIN ? Math.min(AI_OUTPUT_TOKENS_MAX, Math.round(rawMaxOutputTokens)) : undefined;
return {
...defaultConfigs[provider],
...config,
@@ -500,6 +504,7 @@ export function normalizeAiConfig(config: Partial | null | undefined):
proxyUrl: config?.proxyUrl ?? "",
enableThinking: config?.enableThinking ?? true,
reasoningLevel: normalizeAiReasoningLevel(config?.reasoningLevel),
+ maxOutputTokens,
contextWindow: config?.contextWindow ?? undefined,
codexCliPath: config?.codexCliPath?.trim() || undefined,
codexCliEnv: normalizeAiEnv(config?.codexCliEnv),
diff --git a/apps/desktop/src/types/ai.ts b/apps/desktop/src/types/ai.ts
index 89068babe3..e55cc8fb4a 100644
--- a/apps/desktop/src/types/ai.ts
+++ b/apps/desktop/src/types/ai.ts
@@ -42,6 +42,8 @@ export interface AiConfig {
proxyUrl?: string;
enableThinking?: boolean;
reasoningLevel?: AiReasoningLevel;
+ /** Optional per-configuration output budget sent as max_tokens/max_output_tokens. */
+ maxOutputTokens?: number;
contextWindow?: number;
codexCliPath?: string | null;
codexCliEnv?: Record;
diff --git a/crates/dbx-core/src/ai.rs b/crates/dbx-core/src/ai.rs
index e04fae23b1..510d43f155 100644
--- a/crates/dbx-core/src/ai.rs
+++ b/crates/dbx-core/src/ai.rs
@@ -399,6 +399,9 @@ pub struct AiConfig {
/// persist this field; it is attached to a runtime config clone.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub runtime_effort: Option,
+ /// Optional output token budget configured for this provider.
+ #[serde(default)]
+ pub max_output_tokens: Option,
#[serde(default)]
pub context_window: Option,
/// Maximum number of automatic retries on transient API errors
@@ -1214,8 +1217,12 @@ pub fn responses_stream_text(event: &serde_json::Value) -> Option<&str> {
event["delta"].as_str().filter(|s| !s.is_empty())
}
-fn responses_max_output_tokens(max_tokens: Option) -> u32 {
- max_tokens.unwrap_or(2048).max(16)
+fn output_token_limit(max_tokens: Option, config: &AiConfig, default: u32) -> u32 {
+ max_tokens.or(config.max_output_tokens).unwrap_or(default)
+}
+
+fn responses_max_output_tokens(max_tokens: Option, config: &AiConfig) -> u32 {
+ output_token_limit(max_tokens, config, 2048).max(16)
}
fn responses_token_usage(event: &serde_json::Value) -> Option {
@@ -1247,7 +1254,8 @@ fn uses_chat_completion_max_completion_tokens(config: &AiConfig) -> bool {
|| is_openai_api_config(config) && is_openai_reasoning_model(&config.model)
}
-fn set_chat_completion_token_limit(body: &mut serde_json::Value, config: &AiConfig, max_tokens: u32) {
+fn set_chat_completion_token_limit(body: &mut serde_json::Value, config: &AiConfig, max_tokens: Option) {
+ let max_tokens = output_token_limit(max_tokens, config, 4096);
if uses_chat_completion_max_completion_tokens(config) {
body["max_completion_tokens"] = json!(max_tokens);
} else {
@@ -1261,7 +1269,7 @@ fn apply_minimax_chat_completion_fields(body: &mut serde_json::Value, config: &A
}
}
-fn decorate_chat_completion_body(body: &mut serde_json::Value, config: &AiConfig, max_tokens: u32) {
+fn decorate_chat_completion_body(body: &mut serde_json::Value, config: &AiConfig, max_tokens: Option) {
set_chat_completion_token_limit(body, config, max_tokens);
apply_minimax_chat_completion_fields(body, config);
apply_chat_completion_thinking_toggle(body, config);
@@ -2133,7 +2141,7 @@ pub async fn resolve_model_effort_core(config: &AiConfig, model_id: &str) -> Res
pub async fn call_claude(client: &reqwest::Client, request: AiCompletionRequest) -> Result {
let mut body = json!({
"model": claude_http_model(&request.config.model),
- "max_tokens": request.max_tokens.unwrap_or(2048),
+ "max_tokens": output_token_limit(request.max_tokens, &request.config, 2048),
"system": claude_system_prompt(&request.system_prompt),
"messages": claude_messages(&request.messages),
});
@@ -2213,7 +2221,7 @@ pub async fn call_openai_compatible(client: &reqwest::Client, request: AiComplet
"model": request.config.model,
"messages": messages,
});
- decorate_chat_completion_body(&mut body_obj, &request.config, request.max_tokens.unwrap_or(2048));
+ decorate_chat_completion_body(&mut body_obj, &request.config, request.max_tokens);
let res = client
.post(resolve_endpoint(&request.config))
@@ -2238,7 +2246,7 @@ pub async fn call_responses_api(client: &reqwest::Client, request: AiCompletionR
let mut body = json!({
"model": request.config.model,
"input": build_responses_input(&request.system_prompt, &request.messages),
- "max_output_tokens": responses_max_output_tokens(request.max_tokens),
+ "max_output_tokens": responses_max_output_tokens(request.max_tokens, &request.config),
});
crate::ai_effort::apply_runtime_effort(&mut body, &request.config);
@@ -2268,7 +2276,7 @@ pub async fn call_gemini(client: &reqwest::Client, request: AiCompletionRequest)
},
"contents": contents,
"generationConfig": {
- "maxOutputTokens": request.max_tokens.unwrap_or(2048),
+ "maxOutputTokens": output_token_limit(request.max_tokens, &request.config, 2048),
},
});
crate::ai_effort::apply_runtime_effort(&mut body, &request.config);
@@ -2787,7 +2795,7 @@ pub async fn test_connection_core(config: &AiConfig) -> Result Result<(), String> {
let mut body = json!({
"model": claude_http_model(&request.config.model),
- "max_tokens": request.max_tokens.unwrap_or(2048),
+ "max_tokens": output_token_limit(request.max_tokens, &request.config, 2048),
"system": claude_system_prompt(&request.system_prompt),
"messages": claude_messages(&request.messages),
"stream": true,
@@ -3275,7 +3283,7 @@ async fn stream_openai(
"messages": messages,
"stream": true,
});
- decorate_chat_completion_body(&mut body_obj, &request.config, request.max_tokens.unwrap_or(2048));
+ decorate_chat_completion_body(&mut body_obj, &request.config, request.max_tokens);
let endpoint = resolve_endpoint(&request.config);
let config = request.config.clone();
@@ -3406,7 +3414,7 @@ async fn stream_responses_api(
let mut body = json!({
"model": request.config.model,
"input": build_responses_input(&request.system_prompt, &request.messages),
- "max_output_tokens": responses_max_output_tokens(request.max_tokens),
+ "max_output_tokens": responses_max_output_tokens(request.max_tokens, &request.config),
"stream": true,
});
crate::ai_effort::apply_runtime_effort(&mut body, &request.config);
@@ -3498,7 +3506,7 @@ async fn stream_gemini(
},
"contents": contents,
"generationConfig": {
- "maxOutputTokens": request.max_tokens.unwrap_or(2048),
+ "maxOutputTokens": output_token_limit(request.max_tokens, &request.config, 2048),
},
});
crate::ai_effort::apply_runtime_effort(&mut body, &request.config);
@@ -3733,7 +3741,7 @@ async fn stream_claude_with_tools(
let mut body = json!({
"model": claude_http_model(&request.config.model),
- "max_tokens": request.max_tokens.unwrap_or(4096),
+ "max_tokens": output_token_limit(request.max_tokens, &request.config, 4096),
"system": claude_system_prompt(&request.system_prompt),
"messages": messages,
"tools": tool_json,
@@ -3918,7 +3926,7 @@ async fn stream_openai_with_tools(
"stream": true,
"stream_options": { "include_usage": true },
});
- set_chat_completion_token_limit(&mut body, &request.config, request.max_tokens.unwrap_or(4096));
+ set_chat_completion_token_limit(&mut body, &request.config, request.max_tokens);
apply_minimax_chat_completion_fields(&mut body, &request.config);
if request.config.runtime_effort.is_none() && !request.config.enable_thinking {
@@ -4094,7 +4102,7 @@ async fn stream_responses_with_tools(
let mut body = json!({
"model": request.config.model,
"input": build_responses_input_with_tools(&request.system_prompt, &request.messages),
- "max_output_tokens": responses_max_output_tokens(request.max_tokens),
+ "max_output_tokens": responses_max_output_tokens(request.max_tokens, &request.config),
"tools": tool_json,
"tool_choice": "auto",
"stream": true,
@@ -4346,7 +4354,7 @@ async fn stream_gemini_with_tools(
"systemInstruction": { "parts": [{ "text": request.system_prompt }] },
"tools": [{ "functionDeclarations": tool_declarations }],
"generationConfig": {
- "maxOutputTokens": request.max_tokens.unwrap_or(4096),
+ "maxOutputTokens": output_token_limit(request.max_tokens, &request.config, 4096),
}
});
crate::ai_effort::apply_runtime_effort(&mut body, &request.config);
@@ -4603,6 +4611,27 @@ mod tests {
assert_eq!(openai_message_content(&message), serde_json::json!(marker));
}
+ #[test]
+ fn ai_config_max_output_tokens_uses_camel_case_and_defaults_to_none() {
+ let configured: AiConfig = serde_json::from_value(serde_json::json!({
+ "provider": "deepseek",
+ "model": "deepseek-v4-flash",
+ "maxOutputTokens": 32_768
+ }))
+ .unwrap();
+ assert_eq!(configured.max_output_tokens, Some(32_768));
+
+ let legacy: AiConfig = serde_json::from_value(serde_json::json!({
+ "provider": "deepseek",
+ "model": "deepseek-v4-flash"
+ }))
+ .unwrap();
+ assert_eq!(legacy.max_output_tokens, None);
+
+ let serialized = serde_json::to_value(configured).unwrap();
+ assert_eq!(serialized["maxOutputTokens"], serde_json::json!(32_768));
+ }
+
#[test]
fn ai_chat_selection_default_mode_serde() {
// Old blobs without a defaultMode field load as None (no migration needed).
@@ -4842,6 +4871,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: Some(1_000_000),
max_retries: None,
@@ -5505,6 +5535,7 @@ mod tests {
proxy_url: "not a proxy url".to_string(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5546,6 +5577,7 @@ mod tests {
proxy_url: "127.0.0.1:7890".to_string(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5585,6 +5617,7 @@ mod tests {
proxy_url: "not a proxy url".to_string(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5624,6 +5657,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5667,6 +5701,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5707,6 +5742,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5731,7 +5767,7 @@ mod tests {
for provider in
[AiProvider::Ollama, AiProvider::AnthropicCompatible, AiProvider::OpenaiCompatible, AiProvider::Custom]
{
- let config = AiConfig { provider, ..base.clone() };
+ let config = AiConfig { max_output_tokens: None, provider, ..base.clone() };
assert!(validate_config(&config).is_ok());
assert!(validate_model_list_config(&config).is_ok());
assert!(maybe_bearer_headers(&config).unwrap().get(AUTHORIZATION).is_none());
@@ -5745,7 +5781,7 @@ mod tests {
AiProvider::Qwen,
AiProvider::MiniMax,
] {
- let config = AiConfig { provider, ..base.clone() };
+ let config = AiConfig { max_output_tokens: None, provider, ..base.clone() };
assert_eq!(validate_config(&config).unwrap_err(), "API key is required");
assert_eq!(validate_model_list_config(&config).unwrap_err(), "API key is required");
}
@@ -5766,6 +5802,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5801,6 +5838,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5839,6 +5877,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5864,19 +5903,24 @@ mod tests {
assert_eq!(resolve_endpoint(&config), "https://gateway.example.com/anthropic/v1/messages");
assert_eq!(resolve_model_list_endpoint(&config).unwrap(), "https://gateway.example.com/anthropic/v1/models");
- let full_messages =
- AiConfig { endpoint: "https://gateway.example.com/anthropic/v1/messages".to_string(), ..config.clone() };
+ let full_messages = AiConfig {
+ max_output_tokens: None,
+ endpoint: "https://gateway.example.com/anthropic/v1/messages".to_string(),
+ ..config.clone()
+ };
assert_eq!(resolve_endpoint(&full_messages), "https://gateway.example.com/anthropic/v1/messages");
assert_eq!(
resolve_model_list_endpoint(&full_messages).unwrap(),
"https://gateway.example.com/anthropic/v1/models"
);
- let bare_origin = AiConfig { endpoint: "https://gateway.example.com".to_string(), ..config.clone() };
+ let bare_origin =
+ AiConfig { max_output_tokens: None, endpoint: "https://gateway.example.com".to_string(), ..config.clone() };
assert_eq!(resolve_endpoint(&bare_origin), "https://gateway.example.com/v1/messages");
assert_eq!(resolve_model_list_endpoint(&bare_origin).unwrap(), "https://gateway.example.com/v1/models");
let kimi_coding = AiConfig {
+ max_output_tokens: None,
endpoint: "https://api.kimi.com/coding/".to_string(),
model: "kimi-for-coding".to_string(),
..config.clone()
@@ -5893,16 +5937,26 @@ mod tests {
assert_eq!(resolve_endpoint(&config), "https://gateway.example.com/v1/messages");
assert_eq!(resolve_model_list_endpoint(&config).unwrap(), "https://gateway.example.com/v1/models");
- let v1 = AiConfig { endpoint: "https://gateway.example.com/v1".to_string(), ..config.clone() };
+ let v1 = AiConfig {
+ max_output_tokens: None,
+ endpoint: "https://gateway.example.com/v1".to_string(),
+ ..config.clone()
+ };
assert_eq!(resolve_endpoint(&v1), "https://gateway.example.com/v1/messages");
- let nested =
- AiConfig { endpoint: "https://gateway.example.com/anthropic/v1/messages".to_string(), ..config.clone() };
+ let nested = AiConfig {
+ max_output_tokens: None,
+ endpoint: "https://gateway.example.com/anthropic/v1/messages".to_string(),
+ ..config.clone()
+ };
assert_eq!(resolve_endpoint(&nested), "https://gateway.example.com/anthropic/v1/messages");
assert_eq!(resolve_model_list_endpoint(&nested).unwrap(), "https://gateway.example.com/anthropic/v1/models");
- let dashscope =
- AiConfig { endpoint: "https://dashscope-intl.aliyuncs.com/apps/anthropic".to_string(), ..config.clone() };
+ let dashscope = AiConfig {
+ max_output_tokens: None,
+ endpoint: "https://dashscope-intl.aliyuncs.com/apps/anthropic".to_string(),
+ ..config.clone()
+ };
assert_eq!(resolve_endpoint(&dashscope), "https://dashscope-intl.aliyuncs.com/apps/anthropic/v1/messages");
assert_eq!(
resolve_model_list_endpoint(&dashscope).unwrap(),
@@ -5929,6 +5983,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -5955,14 +6010,15 @@ mod tests {
assert_eq!(resolve_model_list_endpoint(&config).unwrap(), "https://api.minimax.io/v1/models");
assert!(provider_requires_api_key(&config.provider));
- let china = AiConfig { endpoint: "https://api.minimaxi.com/v1".to_string(), ..config.clone() };
+ let china =
+ AiConfig { max_output_tokens: None, endpoint: "https://api.minimaxi.com/v1".to_string(), ..config.clone() };
assert_eq!(resolve_endpoint(&china), "https://api.minimaxi.com/v1/chat/completions");
assert_eq!(resolve_model_list_endpoint(&china).unwrap(), "https://api.minimaxi.com/v1/models");
- let no_key = AiConfig { api_key: String::new(), ..config.clone() };
+ let no_key = AiConfig { max_output_tokens: None, api_key: String::new(), ..config.clone() };
assert_eq!(validate_config(&no_key).unwrap_err(), "API key is required");
- let responses = AiConfig { api_style: AiApiStyle::Responses, ..config.clone() };
+ let responses = AiConfig { max_output_tokens: None, api_style: AiApiStyle::Responses, ..config.clone() };
assert_eq!(
validate_config(&responses).unwrap_err(),
"MiniMax currently supports the Chat Completions API style in DBX; select Completions and retry"
@@ -5976,6 +6032,7 @@ mod tests {
#[test]
fn kimi_provider_uses_openai_compatible_endpoints_and_requires_an_api_key() {
let config = AiConfig {
+ max_output_tokens: None,
provider: AiProvider::Kimi,
api_key: "key".to_string(),
auth_method: AiAuthMethod::Bearer,
@@ -6013,6 +6070,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -6037,25 +6095,31 @@ mod tests {
assert_eq!(resolve_model_list_endpoint(&config).unwrap(), "https://api.example.com/v1/models");
// Endpoint with /v1 already present — no change
- let config_v1 = AiConfig { endpoint: "https://api.example.com/v1".to_string(), ..config.clone() };
+ let config_v1 =
+ AiConfig { max_output_tokens: None, endpoint: "https://api.example.com/v1".to_string(), ..config.clone() };
assert_eq!(resolve_endpoint(&config_v1), "https://api.example.com/v1/chat/completions");
assert_eq!(resolve_model_list_endpoint(&config_v1).unwrap(), "https://api.example.com/v1/models");
// Endpoint with /v2 — no change
- let config_v2 = AiConfig { endpoint: "https://api.example.com/v2".to_string(), ..config.clone() };
+ let config_v2 =
+ AiConfig { max_output_tokens: None, endpoint: "https://api.example.com/v2".to_string(), ..config.clone() };
assert_eq!(resolve_endpoint(&config_v2), "https://api.example.com/v2/chat/completions");
// Full path already specified — no change
- let config_full =
- AiConfig { endpoint: "https://api.openai.com/v1/chat/completions".to_string(), ..config.clone() };
+ let config_full = AiConfig {
+ max_output_tokens: None,
+ endpoint: "https://api.openai.com/v1/chat/completions".to_string(),
+ ..config.clone()
+ };
assert_eq!(resolve_endpoint(&config_full), "https://api.openai.com/v1/chat/completions");
// Responses API style with /v1 missing
- let config_responses = AiConfig { api_style: AiApiStyle::Responses, ..config.clone() };
+ let config_responses = AiConfig { max_output_tokens: None, api_style: AiApiStyle::Responses, ..config.clone() };
assert_eq!(resolve_endpoint(&config_responses), "https://api.example.com/v1/responses");
// Ollama preset already has /v1 — no change
let ollama = AiConfig {
+ max_output_tokens: None,
provider: AiProvider::Ollama,
endpoint: "http://localhost:11434/v1".to_string(),
..config.clone()
@@ -6063,12 +6127,14 @@ mod tests {
assert_eq!(resolve_endpoint(&ollama), "http://localhost:11434/v1/chat/completions");
// Custom path without /v1 — left alone (CC-Switch strategy: only bare origin gets auto /v1)
- let custom_path = AiConfig { endpoint: "https://my-gateway.com/api".to_string(), ..config.clone() };
+ let custom_path =
+ AiConfig { max_output_tokens: None, endpoint: "https://my-gateway.com/api".to_string(), ..config.clone() };
assert_eq!(resolve_endpoint(&custom_path), "https://my-gateway.com/api/chat/completions");
assert_eq!(resolve_model_list_endpoint(&custom_path).unwrap(), "https://my-gateway.com/api/models");
// Bare host with port — add /v1
- let bare_with_port = AiConfig { endpoint: "http://localhost:8080".to_string(), ..config.clone() };
+ let bare_with_port =
+ AiConfig { max_output_tokens: None, endpoint: "http://localhost:8080".to_string(), ..config.clone() };
assert_eq!(resolve_endpoint(&bare_with_port), "http://localhost:8080/v1/chat/completions");
}
@@ -6087,6 +6153,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -6111,6 +6178,7 @@ mod tests {
assert_eq!(resolve_endpoint(&config), "https://api.openai.com/v1/responses");
let completions = AiConfig {
+ max_output_tokens: None,
endpoint: "https://api.openai.com/v1/responses".to_string(),
api_style: AiApiStyle::Completions,
custom_headers: Default::default(),
@@ -6134,6 +6202,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -6304,6 +6373,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -6326,7 +6396,8 @@ mod tests {
};
assert_eq!(resolve_ollama_show_endpoint(&config).unwrap(), "http://localhost:11434/api/show");
- let prefixed = AiConfig { endpoint: "https://example.com/ollama/v1".to_string(), ..config };
+ let prefixed =
+ AiConfig { max_output_tokens: None, endpoint: "https://example.com/ollama/v1".to_string(), ..config };
assert_eq!(resolve_ollama_show_endpoint(&prefixed).unwrap(), "https://example.com/ollama/api/show");
}
@@ -6347,6 +6418,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -6439,6 +6511,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -6473,10 +6546,15 @@ mod tests {
#[test]
fn responses_api_clamps_tiny_output_token_requests() {
- assert_eq!(responses_max_output_tokens(Some(1)), 16);
- assert_eq!(responses_max_output_tokens(Some(16)), 16);
- assert_eq!(responses_max_output_tokens(Some(2400)), 2400);
- assert_eq!(responses_max_output_tokens(None), 2048);
+ let mut config = minimax_test_config("https://api.minimaxi.com/v1");
+ assert_eq!(responses_max_output_tokens(Some(1), &config), 16);
+ assert_eq!(responses_max_output_tokens(Some(16), &config), 16);
+ assert_eq!(responses_max_output_tokens(Some(2400), &config), 2400);
+ assert_eq!(responses_max_output_tokens(None, &config), 2048);
+
+ config.max_output_tokens = Some(32_768);
+ assert_eq!(responses_max_output_tokens(None, &config), 32_768);
+ assert_eq!(responses_max_output_tokens(Some(2400), &config), 2400);
}
#[test]
@@ -6717,6 +6795,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -6743,7 +6822,7 @@ mod tests {
"messages": [{ "role": "user", "content": TEST_PROMPT }],
"stream": true,
});
- set_chat_completion_token_limit(&mut body, &config, 1024);
+ set_chat_completion_token_limit(&mut body, &config, Some(1024));
assert_eq!(body.get("max_completion_tokens"), Some(&serde_json::json!(1024)));
assert!(body.get("max_tokens").is_none());
@@ -6754,7 +6833,7 @@ mod tests {
"messages": [{ "role": "user", "content": TEST_PROMPT }],
"stream": true,
});
- set_chat_completion_token_limit(&mut body, &config, 1024);
+ set_chat_completion_token_limit(&mut body, &config, Some(1024));
assert_eq!(body.get("max_tokens"), Some(&serde_json::json!(1024)));
assert!(body.get("max_completion_tokens").is_none());
@@ -6766,7 +6845,7 @@ mod tests {
"messages": [{ "role": "user", "content": TEST_PROMPT }],
"stream": true,
});
- set_chat_completion_token_limit(&mut body, &config, 1024);
+ set_chat_completion_token_limit(&mut body, &config, Some(1024));
assert_eq!(body.get("max_completion_tokens"), Some(&serde_json::json!(1024)));
assert!(body.get("max_tokens").is_none());
@@ -6779,10 +6858,19 @@ mod tests {
"messages": [{ "role": "user", "content": TEST_PROMPT }],
"stream": true,
});
- set_chat_completion_token_limit(&mut body, &config, 1024);
+ set_chat_completion_token_limit(&mut body, &config, Some(1024));
assert_eq!(body.get("max_tokens"), Some(&serde_json::json!(1024)));
assert!(body.get("max_completion_tokens").is_none());
+
+ config.max_output_tokens = Some(32_768);
+ let mut body = serde_json::json!({
+ "model": &config.model,
+ "messages": [{ "role": "user", "content": TEST_PROMPT }],
+ "stream": true,
+ });
+ set_chat_completion_token_limit(&mut body, &config, None);
+ assert_eq!(body.get("max_tokens"), Some(&serde_json::json!(32_768)));
}
#[test]
@@ -6939,6 +7027,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -6990,6 +7079,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -7044,6 +7134,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -7092,6 +7183,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -7127,7 +7219,7 @@ mod tests {
config.enable_thinking = false;
let mut body = serde_json::json!({ "model": &config.model });
- decorate_chat_completion_body(&mut body, &config, 1024);
+ decorate_chat_completion_body(&mut body, &config, Some(1024));
assert_eq!(body["reasoning_split"], true);
assert_eq!(body["max_completion_tokens"], 1024);
@@ -7484,6 +7576,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::High,
+ max_output_tokens: None,
runtime_effort: Some(AiEffortSelection::ProviderDefault),
context_window: None,
max_retries: None,
@@ -7559,6 +7652,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -7629,6 +7723,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/ai_claude_code_cli.rs b/crates/dbx-core/src/ai_claude_code_cli.rs
index 6cd0f444cb..46e816b1ae 100644
--- a/crates/dbx-core/src/ai_claude_code_cli.rs
+++ b/crates/dbx-core/src/ai_claude_code_cli.rs
@@ -573,6 +573,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/ai_codebuddy_cli.rs b/crates/dbx-core/src/ai_codebuddy_cli.rs
index fe752fb90d..786cd4a069 100644
--- a/crates/dbx-core/src/ai_codebuddy_cli.rs
+++ b/crates/dbx-core/src/ai_codebuddy_cli.rs
@@ -928,6 +928,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/ai_codex_cli.rs b/crates/dbx-core/src/ai_codex_cli.rs
index d16818427a..cb1617d258 100644
--- a/crates/dbx-core/src/ai_codex_cli.rs
+++ b/crates/dbx-core/src/ai_codex_cli.rs
@@ -1119,6 +1119,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/ai_cursor_cli.rs b/crates/dbx-core/src/ai_cursor_cli.rs
index d49cef3a70..e420ab09b9 100644
--- a/crates/dbx-core/src/ai_cursor_cli.rs
+++ b/crates/dbx-core/src/ai_cursor_cli.rs
@@ -410,6 +410,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/ai_effort.rs b/crates/dbx-core/src/ai_effort.rs
index 26a9992778..6651e9681f 100644
--- a/crates/dbx-core/src/ai_effort.rs
+++ b/crates/dbx-core/src/ai_effort.rs
@@ -469,6 +469,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/ai_grok_cli.rs b/crates/dbx-core/src/ai_grok_cli.rs
index 60e48a8ae8..71e6edfa90 100644
--- a/crates/dbx-core/src/ai_grok_cli.rs
+++ b/crates/dbx-core/src/ai_grok_cli.rs
@@ -790,6 +790,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/ai_opencode_cli.rs b/crates/dbx-core/src/ai_opencode_cli.rs
index 0ace5290c0..a1657b3441 100644
--- a/crates/dbx-core/src/ai_opencode_cli.rs
+++ b/crates/dbx-core/src/ai_opencode_cli.rs
@@ -456,6 +456,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/ai_pi_agent_cli.rs b/crates/dbx-core/src/ai_pi_agent_cli.rs
index 2a8c45db1e..6529d955cb 100644
--- a/crates/dbx-core/src/ai_pi_agent_cli.rs
+++ b/crates/dbx-core/src/ai_pi_agent_cli.rs
@@ -864,6 +864,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::High,
+ max_output_tokens: None,
runtime_effort: Some(AiEffortSelection::Enum("high".to_string())),
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/ai_qoder_cli.rs b/crates/dbx-core/src/ai_qoder_cli.rs
index b4ce8c96f0..d176ca5f8b 100644
--- a/crates/dbx-core/src/ai_qoder_cli.rs
+++ b/crates/dbx-core/src/ai_qoder_cli.rs
@@ -532,6 +532,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/cloud_sync.rs b/crates/dbx-core/src/cloud_sync.rs
index a34d48b851..75622f1d48 100644
--- a/crates/dbx-core/src/cloud_sync.rs
+++ b/crates/dbx-core/src/cloud_sync.rs
@@ -1639,6 +1639,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: crate::ai::AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-core/src/storage.rs b/crates/dbx-core/src/storage.rs
index 822ad06534..56b1499393 100644
--- a/crates/dbx-core/src/storage.rs
+++ b/crates/dbx-core/src/storage.rs
@@ -7582,6 +7582,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/crates/dbx-web/src/routes/ai.rs b/crates/dbx-web/src/routes/ai.rs
index f6fb110861..d14384e184 100644
--- a/crates/dbx-web/src/routes/ai.rs
+++ b/crates/dbx-web/src/routes/ai.rs
@@ -526,6 +526,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: true,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
@@ -633,6 +634,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,
diff --git a/src-tauri/src/commands/ai.rs b/src-tauri/src/commands/ai.rs
index 041a1010a9..d5e2718353 100644
--- a/src-tauri/src/commands/ai.rs
+++ b/src-tauri/src/commands/ai.rs
@@ -670,6 +670,7 @@ mod tests {
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
+ max_output_tokens: None,
runtime_effort: None,
context_window: None,
max_retries: None,