diff --git a/src/agent/model.ts b/src/agent/model.ts index 9ea450e..f71d821 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -2,6 +2,7 @@ * Model resolution and handling utilities */ import modelsData from "../models.json"; +import { OPENAI_CODEX_PROVIDER_NAME } from "../providers/openai-codex-provider"; export const models = modelsData; @@ -186,6 +187,68 @@ export function getModelUpdateArgs( return modelInfo?.updateArgs; } +type AgentModelSnapshot = { + model?: string | null; + llm_config?: { + model?: string | null; + model_endpoint_type?: string | null; + reasoning_effort?: string | null; + enable_reasoner?: boolean | null; + } | null; +}; + +/** + * Resolve the current model preset + updateArgs for an existing agent. + * + * Used during startup/resume refresh to re-apply only preset-defined fields + * (without requiring an explicit --model flag). + */ +export function getModelPresetUpdateForAgent( + agent: AgentModelSnapshot, +): { modelHandle: string; updateArgs: Record } | null { + const directHandle = + typeof agent.model === "string" && agent.model.length > 0 + ? agent.model + : null; + + const endpointType = agent.llm_config?.model_endpoint_type; + const llmModel = agent.llm_config?.model; + const llmDerivedHandle = + typeof endpointType === "string" && + endpointType.length > 0 && + typeof llmModel === "string" && + llmModel.length > 0 + ? `${ + endpointType === "chatgpt_oauth" + ? OPENAI_CODEX_PROVIDER_NAME + : endpointType + }/${llmModel}` + : typeof llmModel === "string" && llmModel.includes("/") + ? llmModel + : null; + + const modelHandle = directHandle ?? llmDerivedHandle; + if (!modelHandle) return null; + + const modelInfo = getModelInfoForLlmConfig(modelHandle, { + reasoning_effort: agent.llm_config?.reasoning_effort ?? null, + enable_reasoner: agent.llm_config?.enable_reasoner ?? null, + }); + + const updateArgs = + (modelInfo?.updateArgs as Record | undefined) ?? + getModelUpdateArgs(modelHandle); + + if (!updateArgs || Object.keys(updateArgs).length === 0) { + return null; + } + + return { + modelHandle: modelInfo?.handle ?? modelHandle, + updateArgs, + }; +} + /** * Find a model entry by handle with fuzzy matching support * @param handle - The full model handle diff --git a/src/headless.ts b/src/headless.ts index faf29ea..ac44b79 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -878,23 +878,56 @@ export async function handleHeadlessCommand( (!forceNew && !fromAfFile) ); - // If resuming and a model or system prompt was specified, apply those changes - if (isResumingAgent && (model || systemPromptPreset)) { + // If resuming, always refresh model settings from presets to keep + // preset-derived fields in sync, then apply optional command-line + // overrides (model/system prompt). + if (isResumingAgent) { + const { updateAgentLLMConfig } = await import("./agent/modify"); + if (model) { const { resolveModel } = await import("./agent/model"); const modelHandle = resolveModel(model); - if (!modelHandle) { + if (typeof modelHandle !== "string") { console.error(`Error: Invalid model "${model}"`); process.exit(1); } // Always apply model update - different model IDs can share the same // handle but have different settings (e.g., gpt-5.2-medium vs gpt-5.2-xhigh) - const { updateAgentLLMConfig } = await import("./agent/modify"); const updateArgs = getModelUpdateArgs(model); await updateAgentLLMConfig(agent.id, modelHandle, updateArgs); // Refresh agent state after model update agent = await client.agents.retrieve(agent.id); + } else { + const { getModelPresetUpdateForAgent } = await import("./agent/model"); + const presetRefresh = getModelPresetUpdateForAgent(agent); + if (presetRefresh) { + // Resume preset refresh is intentionally scoped for now. + // We only force-refresh max_output_tokens + parallel_tool_calls. + // Other preset fields available in models.json (for example: + // context_window, reasoning_effort, enable_reasoner, + // max_reasoning_tokens, verbosity, temperature, + // thinking_budget) are intentionally not auto-applied yet. + const resumeRefreshUpdateArgs: Record = {}; + if (typeof presetRefresh.updateArgs.max_output_tokens === "number") { + resumeRefreshUpdateArgs.max_output_tokens = + presetRefresh.updateArgs.max_output_tokens; + } + if (typeof presetRefresh.updateArgs.parallel_tool_calls === "boolean") { + resumeRefreshUpdateArgs.parallel_tool_calls = + presetRefresh.updateArgs.parallel_tool_calls; + } + + if (Object.keys(resumeRefreshUpdateArgs).length > 0) { + await updateAgentLLMConfig( + agent.id, + presetRefresh.modelHandle, + resumeRefreshUpdateArgs, + ); + // Refresh agent state after model update + agent = await client.agents.retrieve(agent.id); + } + } } if (systemPromptPreset) { diff --git a/src/index.ts b/src/index.ts index ebe2920..508ea40 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1789,8 +1789,12 @@ async function main(): Promise { ); setIsResumingSession(resuming); - // If resuming and a model or system prompt was specified, apply those changes - if (resuming && (model || systemPromptPreset)) { + // If resuming, always refresh model settings from presets to keep + // preset-derived fields in sync, then apply optional command-line + // overrides (model/system prompt). + if (resuming) { + const { updateAgentLLMConfig } = await import("./agent/modify"); + if (model) { const { resolveModel, getModelUpdateArgs } = await import( "./agent/model" @@ -1803,11 +1807,47 @@ async function main(): Promise { // Always apply model update - different model IDs can share the same // handle but have different settings (e.g., gpt-5.2-medium vs gpt-5.2-xhigh) - const { updateAgentLLMConfig } = await import("./agent/modify"); const updateArgs = getModelUpdateArgs(model); await updateAgentLLMConfig(agent.id, modelHandle, updateArgs); // Refresh agent state after model update agent = await client.agents.retrieve(agent.id); + } else { + const { getModelPresetUpdateForAgent } = await import( + "./agent/model" + ); + const presetRefresh = getModelPresetUpdateForAgent(agent); + if (presetRefresh) { + // Resume preset refresh is intentionally scoped for now. + // We only force-refresh max_output_tokens + parallel_tool_calls. + // Other preset fields available in models.json (for example: + // context_window, reasoning_effort, enable_reasoner, + // max_reasoning_tokens, verbosity, temperature, + // thinking_budget) are intentionally not auto-applied yet. + const resumeRefreshUpdateArgs: Record = {}; + if ( + typeof presetRefresh.updateArgs.max_output_tokens === "number" + ) { + resumeRefreshUpdateArgs.max_output_tokens = + presetRefresh.updateArgs.max_output_tokens; + } + if ( + typeof presetRefresh.updateArgs.parallel_tool_calls === + "boolean" + ) { + resumeRefreshUpdateArgs.parallel_tool_calls = + presetRefresh.updateArgs.parallel_tool_calls; + } + + if (Object.keys(resumeRefreshUpdateArgs).length > 0) { + await updateAgentLLMConfig( + agent.id, + presetRefresh.modelHandle, + resumeRefreshUpdateArgs, + ); + // Refresh agent state after model update + agent = await client.agents.retrieve(agent.id); + } + } } if (systemPromptPreset) { diff --git a/src/models.json b/src/models.json index 2ec9ad0..c7f2e94 100644 --- a/src/models.json +++ b/src/models.json @@ -10,7 +10,8 @@ "context_window": 200000, "max_output_tokens": 128000, "reasoning_effort": "high", - "enable_reasoner": true + "enable_reasoner": true, + "parallel_tool_calls": true } }, { @@ -22,7 +23,8 @@ "context_window": 1000000, "max_output_tokens": 128000, "reasoning_effort": "high", - "enable_reasoner": true + "enable_reasoner": true, + "parallel_tool_calls": true } }, { @@ -34,7 +36,8 @@ "context_window": 200000, "max_output_tokens": 128000, "reasoning_effort": "none", - "enable_reasoner": false + "enable_reasoner": false, + "parallel_tool_calls": true } }, { @@ -47,7 +50,8 @@ "max_output_tokens": 128000, "reasoning_effort": "low", "enable_reasoner": true, - "max_reasoning_tokens": 4000 + "max_reasoning_tokens": 4000, + "parallel_tool_calls": true } }, { @@ -60,7 +64,8 @@ "max_output_tokens": 128000, "reasoning_effort": "medium", "enable_reasoner": true, - "max_reasoning_tokens": 12000 + "max_reasoning_tokens": 12000, + "parallel_tool_calls": true } }, { @@ -73,7 +78,8 @@ "max_output_tokens": 128000, "reasoning_effort": "xhigh", "enable_reasoner": true, - "max_reasoning_tokens": 31999 + "max_reasoning_tokens": 31999, + "parallel_tool_calls": true } }, { @@ -84,7 +90,8 @@ "updateArgs": { "context_window": 180000, "max_output_tokens": 64000, - "max_reasoning_tokens": 31999 + "max_reasoning_tokens": 31999, + "parallel_tool_calls": true } }, { @@ -95,7 +102,8 @@ "updateArgs": { "enable_reasoner": false, "context_window": 180000, - "max_output_tokens": 64000 + "max_output_tokens": 64000, + "parallel_tool_calls": true } }, { @@ -108,7 +116,8 @@ "context_window": 200000, "max_output_tokens": 128000, "reasoning_effort": "high", - "enable_reasoner": true + "enable_reasoner": true, + "parallel_tool_calls": true } }, { @@ -120,7 +129,8 @@ "context_window": 200000, "max_output_tokens": 128000, "reasoning_effort": "none", - "enable_reasoner": false + "enable_reasoner": false, + "parallel_tool_calls": true } }, { @@ -133,7 +143,8 @@ "max_output_tokens": 128000, "reasoning_effort": "low", "enable_reasoner": true, - "max_reasoning_tokens": 4000 + "max_reasoning_tokens": 4000, + "parallel_tool_calls": true } }, { @@ -146,7 +157,8 @@ "max_output_tokens": 128000, "reasoning_effort": "medium", "enable_reasoner": true, - "max_reasoning_tokens": 12000 + "max_reasoning_tokens": 12000, + "parallel_tool_calls": true } }, { @@ -159,7 +171,8 @@ "max_output_tokens": 128000, "reasoning_effort": "xhigh", "enable_reasoner": true, - "max_reasoning_tokens": 31999 + "max_reasoning_tokens": 31999, + "parallel_tool_calls": true } }, { @@ -170,7 +183,8 @@ "updateArgs": { "context_window": 180000, "max_output_tokens": 64000, - "max_reasoning_tokens": 31999 + "max_reasoning_tokens": 31999, + "parallel_tool_calls": true } }, { @@ -183,7 +197,8 @@ "updateArgs": { "context_window": 180000, "max_output_tokens": 64000, - "max_reasoning_tokens": 31999 + "max_reasoning_tokens": 31999, + "parallel_tool_calls": true } }, { @@ -194,7 +209,8 @@ "isFeatured": true, "updateArgs": { "context_window": 180000, - "max_output_tokens": 64000 + "max_output_tokens": 64000, + "parallel_tool_calls": true } }, { @@ -206,7 +222,8 @@ "reasoning_effort": "none", "verbosity": "low", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -218,7 +235,8 @@ "reasoning_effort": "low", "verbosity": "low", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -230,7 +248,8 @@ "reasoning_effort": "medium", "verbosity": "low", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -243,7 +262,8 @@ "reasoning_effort": "high", "verbosity": "low", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -255,7 +275,8 @@ "reasoning_effort": "xhigh", "verbosity": "low", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -267,7 +288,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -279,7 +301,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -291,7 +314,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -303,7 +327,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -315,7 +340,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -327,7 +353,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -339,7 +366,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -351,7 +379,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -363,7 +392,8 @@ "reasoning_effort": "xhigh", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -375,7 +405,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -387,7 +418,8 @@ "reasoning_effort": "none", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -399,7 +431,8 @@ "reasoning_effort": "low", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -412,7 +445,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -424,7 +458,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -436,7 +471,8 @@ "reasoning_effort": "xhigh", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -448,7 +484,8 @@ "reasoning_effort": "none", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -460,7 +497,8 @@ "reasoning_effort": "low", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -473,7 +511,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -485,7 +524,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -497,7 +537,8 @@ "reasoning_effort": "xhigh", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -509,7 +550,8 @@ "reasoning_effort": "none", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -521,7 +563,8 @@ "reasoning_effort": "low", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -533,7 +576,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -545,7 +589,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -557,7 +602,8 @@ "reasoning_effort": "none", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -570,7 +616,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -582,7 +629,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -594,7 +642,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -606,7 +655,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -618,7 +668,8 @@ "reasoning_effort": "xhigh", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -630,7 +681,8 @@ "reasoning_effort": "minimal", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -642,7 +694,8 @@ "reasoning_effort": "low", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -654,7 +707,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -666,7 +720,8 @@ "reasoning_effort": "high", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -678,7 +733,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -690,7 +746,8 @@ "reasoning_effort": "medium", "verbosity": "medium", "context_window": 272000, - "max_output_tokens": 128000 + "max_output_tokens": 128000, + "parallel_tool_calls": true } }, { @@ -701,7 +758,8 @@ "isFeatured": true, "free": true, "updateArgs": { - "context_window": 200000 + "context_window": 200000, + "parallel_tool_calls": true } }, { @@ -712,7 +770,8 @@ "isFeatured": true, "free": true, "updateArgs": { - "context_window": 200000 + "context_window": 200000, + "parallel_tool_calls": true } }, { @@ -724,7 +783,8 @@ "free": true, "updateArgs": { "context_window": 160000, - "max_output_tokens": 64000 + "max_output_tokens": 64000, + "parallel_tool_calls": true } }, { @@ -736,7 +796,8 @@ "free": true, "updateArgs": { "context_window": 160000, - "max_output_tokens": 64000 + "max_output_tokens": 64000, + "parallel_tool_calls": true } }, { @@ -746,7 +807,8 @@ "description": "Minimax's latest model", "updateArgs": { "context_window": 160000, - "max_output_tokens": 64000 + "max_output_tokens": 64000, + "parallel_tool_calls": true } }, { @@ -755,7 +817,8 @@ "label": "Kimi K2", "description": "Kimi's K2 model", "updateArgs": { - "context_window": 262144 + "context_window": 262144, + "parallel_tool_calls": true } }, { @@ -766,7 +829,8 @@ "updateArgs": { "context_window": 256000, "max_output_tokens": 16000, - "temperature": 1.0 + "temperature": 1.0, + "parallel_tool_calls": true } }, { @@ -776,7 +840,8 @@ "description": "Kimi's latest coding model", "isFeatured": true, "updateArgs": { - "context_window": 262144 + "context_window": 262144, + "parallel_tool_calls": true } }, { @@ -785,7 +850,8 @@ "label": "DeepSeek Chat V3.1", "description": "DeepSeek V3.1 model", "updateArgs": { - "context_window": 128000 + "context_window": 128000, + "parallel_tool_calls": true } }, { @@ -794,7 +860,11 @@ "label": "Gemini 3.1 Pro", "description": "Google's latest and smartest model", "isFeatured": true, - "updateArgs": { "context_window": 180000, "temperature": 1.0 } + "updateArgs": { + "context_window": 180000, + "temperature": 1.0, + "parallel_tool_calls": true + } }, { "id": "gemini-3", @@ -802,7 +872,11 @@ "label": "Gemini 3 Pro", "description": "Google's smartest model", "isFeatured": true, - "updateArgs": { "context_window": 180000, "temperature": 1.0 } + "updateArgs": { + "context_window": 180000, + "temperature": 1.0, + "parallel_tool_calls": true + } }, { "id": "gemini-3-flash", @@ -810,55 +884,63 @@ "label": "Gemini 3 Flash", "description": "Google's fastest Gemini 3 model", "isFeatured": true, - "updateArgs": { "context_window": 180000, "temperature": 1.0 } + "updateArgs": { + "context_window": 180000, + "temperature": 1.0, + "parallel_tool_calls": true + } }, { "id": "gemini-flash", "handle": "google_ai/gemini-2.5-flash", "label": "Gemini 2.5 Flash", "description": "Google's fastest model", - "updateArgs": { "context_window": 180000 } + "updateArgs": { "context_window": 180000, "parallel_tool_calls": true } }, { "id": "gemini-pro", "handle": "google_ai/gemini-2.5-pro", "label": "Gemini 2.5 Pro", "description": "Google's last generation flagship model", - "updateArgs": { "context_window": 180000 } + "updateArgs": { "context_window": 180000, "parallel_tool_calls": true } }, { "id": "gpt-4.1", "handle": "openai/gpt-4.1", "label": "GPT-4.1", "description": "OpenAI's most recent non-reasoner model", - "updateArgs": { "context_window": 1047576 } + "updateArgs": { "context_window": 1047576, "parallel_tool_calls": true } }, { "id": "gpt-4.1-mini", "handle": "openai/gpt-4.1-mini-2025-04-14", "label": "GPT-4.1-Mini", "description": "OpenAI's most recent non-reasoner model (mini version)", - "updateArgs": { "context_window": 1047576 } + "updateArgs": { "context_window": 1047576, "parallel_tool_calls": true } }, { "id": "gpt-4.1-nano", "handle": "openai/gpt-4.1-nano-2025-04-14", "label": "GPT-4.1-Nano", "description": "OpenAI's most recent non-reasoner model (nano version)", - "updateArgs": { "context_window": 1047576 } + "updateArgs": { "context_window": 1047576, "parallel_tool_calls": true } }, { "id": "o4-mini", "handle": "openai/o4-mini", "label": "o4-mini", "description": "OpenAI's latest o-series reasoning model", - "updateArgs": { "context_window": 180000 } + "updateArgs": { "context_window": 180000, "parallel_tool_calls": true } }, { "id": "gemini-3-vertex", "handle": "google_vertex/gemini-3-pro-preview", "label": "Gemini 3 Pro", "description": "Google's smartest Gemini 3 Pro model (via Vertex AI)", - "updateArgs": { "context_window": 180000, "temperature": 1.0 } + "updateArgs": { + "context_window": 180000, + "temperature": 1.0, + "parallel_tool_calls": true + } } ] diff --git a/src/tests/agent/model-preset-refresh.wiring.test.ts b/src/tests/agent/model-preset-refresh.wiring.test.ts new file mode 100644 index 0000000..42858c8 --- /dev/null +++ b/src/tests/agent/model-preset-refresh.wiring.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +describe("model preset refresh wiring", () => { + test("model.ts exports preset refresh helper", () => { + const path = fileURLToPath( + new URL("../../agent/model.ts", import.meta.url), + ); + const source = readFileSync(path, "utf-8"); + + expect(source).toContain("export function getModelPresetUpdateForAgent("); + expect(source).toContain("OPENAI_CODEX_PROVIDER_NAME"); + expect(source).toContain("getModelInfoForLlmConfig(modelHandle"); + }); + + test("modify.ts keeps direct updateArgs-driven model update flow", () => { + const path = fileURLToPath( + new URL("../../agent/modify.ts", import.meta.url), + ); + const source = readFileSync(path, "utf-8"); + + const start = source.indexOf("export async function updateAgentLLMConfig("); + const end = source.indexOf( + "export interface SystemPromptUpdateResult", + start, + ); + expect(start).toBeGreaterThanOrEqual(0); + expect(end).toBeGreaterThan(start); + const updateSegment = source.slice(start, end); + + expect(updateSegment).toContain( + "buildModelSettings(modelHandle, updateArgs)", + ); + expect(updateSegment).toContain("getModelContextWindow(modelHandle)"); + expect(updateSegment).not.toContain( + "const currentAgent = await client.agents.retrieve(", + ); + expect(source).not.toContain( + 'hasUpdateArg(updateArgs, "parallel_tool_calls")', + ); + }); + + test("interactive resume flow refreshes model preset without explicit --model", () => { + const path = fileURLToPath(new URL("../../index.ts", import.meta.url)); + const source = readFileSync(path, "utf-8"); + + expect(source).toContain("if (resuming)"); + expect(source).toContain("getModelPresetUpdateForAgent"); + expect(source).toContain( + "const presetRefresh = getModelPresetUpdateForAgent(agent)", + ); + expect(source).toContain("resumeRefreshUpdateArgs"); + expect(source).toContain("presetRefresh.updateArgs.max_output_tokens"); + expect(source).toContain("presetRefresh.updateArgs.parallel_tool_calls"); + expect(source).toContain("await updateAgentLLMConfig("); + expect(source).toContain("presetRefresh.modelHandle"); + expect(source).not.toContain( + "await updateAgentLLMConfig(\n agent.id,\n presetRefresh.modelHandle,\n presetRefresh.updateArgs,", + ); + }); + + test("headless resume flow refreshes model preset without explicit --model", () => { + const path = fileURLToPath(new URL("../../headless.ts", import.meta.url)); + const source = readFileSync(path, "utf-8"); + + expect(source).toContain("if (isResumingAgent)"); + expect(source).toContain("getModelPresetUpdateForAgent"); + expect(source).toContain( + "const presetRefresh = getModelPresetUpdateForAgent(agent)", + ); + expect(source).toContain("resumeRefreshUpdateArgs"); + expect(source).toContain("presetRefresh.updateArgs.max_output_tokens"); + expect(source).toContain("presetRefresh.updateArgs.parallel_tool_calls"); + expect(source).toContain("await updateAgentLLMConfig("); + expect(source).toContain("presetRefresh.modelHandle"); + expect(source).not.toContain( + "await updateAgentLLMConfig(\n agent.id,\n presetRefresh.modelHandle,\n presetRefresh.updateArgs,", + ); + }); +});