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(() => {

+ +
+ +
+ +

+ {{ t("ai.maxOutputTokensHint", { min: AI_OUTPUT_TOKENS_MIN, max: AI_OUTPUT_TOKENS_MAX }) }} +

+
+
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,