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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/hooks/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -600,15 +600,17 @@ If a value is too large for the environment, it may be omitted (not set). Mux al
</details>

<details>
<summary>task (8)</summary>
<summary>task (10)</summary>

| Env var | JSON path | Type | Description |
| ---------------------------------- | ------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MUX_TOOL_INPUT_AGENT_ID` | `agentId` | string | — |
| `MUX_TOOL_INPUT_MODEL` | `model` | string | Optional model override for the sub-agent, parsed with the same alias logic as the UI (an alias or a full 'provider:model' string). Omit this unless the user explicitly instructed a specific model — by default the sub-agent inherits the parent's model. Do not assume any particular model is available. |
| `MUX_TOOL_INPUT_N` | `n` | number | Optional best-of count. Use n when several agents should try the same prompt independently. Mutually exclusive with variants; omit both for a single task. Only use grouped runs for sub-agents without interfering side effects, such as read-only agents like explore. |
| `MUX_TOOL_INPUT_PROMPT` | `prompt` | string | — |
| `MUX_TOOL_INPUT_RUN_IN_BACKGROUND` | `run_in_background` | boolean | — |
| `MUX_TOOL_INPUT_SUBAGENT_TYPE` | `subagent_type` | string | — |
| `MUX_TOOL_INPUT_THINKING` | `thinking` | string | Optional thinking/reasoning-level override for the sub-agent. Accepts a level name (off, low, medium, high, xhigh, max) or a numeric index (resolved against the chosen model). Omit this unless the user explicitly instructed a specific thinking level — by default the sub-agent inherits the parent's thinking level. |
| `MUX_TOOL_INPUT_TITLE` | `title` | string | — |
| `MUX_TOOL_INPUT_VARIANTS_<INDEX>` | `variants[<INDEX>]` | string | Optional labels for sibling runs of the same prompt template. Use variants when the task should be repeated across labeled lanes such as issue numbers, commit windows, or frontend/backend/tests/docs review lanes. Mutually exclusive with n. When provided, Mux launches one sibling per label and substitutes ${variant} in the prompt. |
| `MUX_TOOL_INPUT_VARIANTS_COUNT` | `variants.length` | number | Number of elements in variants (Optional labels for sibling runs of the same prompt template. Use variants when the task should be repeated across labeled lanes such as issue numbers, commit windows, or frontend/backend/tests/docs review lanes. Mutually exclusive with n. When provided, Mux launches one sibling per label and substitutes ${variant} in the prompt.) |
Expand Down
2 changes: 1 addition & 1 deletion src/browser/features/ChatInput/useCreationWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import {
getModelCapabilities,
getModelCapabilitiesResolved,
} from "@/common/utils/ai/modelCapabilities";
import { normalizeModelInput } from "@/browser/utils/models/normalizeModelInput";
import { normalizeModelInput } from "@/common/utils/ai/normalizeModelInput";
import { resolveDevcontainerSelection } from "@/browser/utils/devcontainerSelection";
import { getErrorMessage } from "@/common/utils/errors";
import { normalizeAgentId } from "@/common/utils/agentIds";
Expand Down
2 changes: 1 addition & 1 deletion src/browser/utils/chatCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import {
} from "@/constants/slashCommands";
import { applyCompactionOverrides } from "@/browser/utils/messages/compactionOptions";
import { resolveCompactionModel } from "@/browser/utils/messages/compactionModelPreference";
import { normalizeModelInput } from "@/browser/utils/models/normalizeModelInput";
import { normalizeModelInput } from "@/common/utils/ai/normalizeModelInput";
import { getExplicitGatewayPrefix, normalizeToCanonical } from "@/common/utils/ai/models";
import type { QueueDispatchMode } from "@/browser/features/ChatInput/types";
import type { ChatAttachment } from "../features/ChatInput/ChatAttachments";
Expand Down
2 changes: 1 addition & 1 deletion src/browser/utils/slashCommands/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { ParsedCommand, SlashCommandDefinition } from "./types";
import { SLASH_COMMAND_DEFINITION_MAP } from "./registry";
import { MODEL_ABBREVIATIONS } from "@/common/constants/knownModels";
import { normalizeModelInput } from "@/browser/utils/models/normalizeModelInput";
import { normalizeModelInput } from "@/common/utils/ai/normalizeModelInput";
import { parseThinkingInput, type ParsedThinkingInput } from "@/common/types/thinking";

/**
Expand Down
2 changes: 1 addition & 1 deletion src/browser/utils/slashCommands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { MODEL_ABBREVIATIONS } from "@/common/constants/knownModels";
import { SLASH_COMMAND_HINTS } from "@/common/constants/slashCommandHints";
import { assert } from "@/common/utils/assert";
import { isExperimentEnabled as readExperimentEnabled } from "@/browser/hooks/useExperiments";
import { normalizeModelInput } from "@/browser/utils/models/normalizeModelInput";
import { normalizeModelInput } from "@/common/utils/ai/normalizeModelInput";
import { parseGoalBudgetInputCents } from "@/common/utils/goals/budgetParser";
import { HEARTBEAT_MAX_INTERVAL_MS, HEARTBEAT_MIN_INTERVAL_MS } from "@/constants/heartbeat";
import { WORKSPACE_ONLY_COMMAND_KEYS } from "@/constants/slashCommands";
Expand Down
16 changes: 16 additions & 0 deletions src/common/utils/tools/toolDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ const TaskAgentIdSchema = z.preprocess(

const TaskToolBestOfCountSchema = z.number().int().min(1).max(20);

// Model/thinking overrides for the spawned sub-agent. Accepted as free-form strings
// so they can be parsed with the SAME logic as the UI (alias resolution for model;
// named levels OR numeric indices for thinking). A numeric thinking value may arrive
// as a JSON number, so coerce it to a string before parsing in the handler.
const TaskToolModelSchema = z.string().trim().min(1);
const TaskToolThinkingSchema = z.preprocess(
(value) => (typeof value === "number" ? String(value) : value),
z.string().trim().min(1)
);

const TaskToolVariantSchema = z.string().trim().min(1);

const TaskToolVariantsSchema = z.array(TaskToolVariantSchema).min(1).max(20);
Expand Down Expand Up @@ -260,6 +270,12 @@ const TaskToolAgentArgsSchema = z
variants: TaskToolVariantsSchema.nullish().describe(
`Optional labels for sibling runs of the same prompt template. Use variants when the task should be repeated across labeled lanes such as issue numbers, commit windows, or frontend/backend/tests/docs review lanes. Mutually exclusive with n. When provided, Mux launches one sibling per label and substitutes ${TASK_VARIANT_PLACEHOLDER} in the prompt.`
),
model: TaskToolModelSchema.nullish().describe(
"Optional model override for the sub-agent, parsed with the same alias logic as the UI (an alias or a full 'provider:model' string). Omit this unless the user explicitly instructed a specific model — by default the sub-agent inherits the parent's model. Do not assume any particular model is available."
),
thinking: TaskToolThinkingSchema.nullish().describe(
"Optional thinking/reasoning-level override for the sub-agent. Accepts a level name (off, low, medium, high, xhigh, max) or a numeric index (resolved against the chosen model). Omit this unless the user explicitly instructed a specific thinking level — by default the sub-agent inherits the parent's thinking level."
),
})
.strict()
.superRefine((args, ctx) => {
Expand Down
33 changes: 4 additions & 29 deletions src/node/acp/slashCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import assert from "node:assert/strict";
import type { AvailableCommand } from "@agentclientprotocol/sdk";
import type { AgentSkillDescriptor } from "@/common/types/agentSkill";
import { SLASH_COMMAND_HINTS } from "@/common/constants/slashCommandHints";
import {
getExplicitGatewayPrefix,
isValidModelFormat,
normalizeToCanonical,
resolveModelAlias,
} from "@/common/utils/ai/models";
import { normalizeModelInput } from "@/common/utils/ai/normalizeModelInput";
import minimist from "minimist";

const CLEAR_COMMAND_NAME = "clear";
Expand Down Expand Up @@ -315,29 +310,9 @@ function parseSkillCommand(
}

function normalizeModelForCommand(modelInput: string): string | null {
const trimmed = modelInput.trim();
if (trimmed.length === 0) {
return null;
}

const resolved = resolveModelAlias(trimmed);
// Explicit gateway scoping is user intent — preserve it for the backend to honor.
const normalized = getExplicitGatewayPrefix(resolved)
? resolved.trim()
: normalizeToCanonical(resolved).trim();

if (!isValidModelFormat(normalized)) {
return null;
}

// Keep ACP slash commands aligned with the rest of model input handling by rejecting
// malformed provider::model strings that happen to satisfy the first-colon check.
const separatorIndex = normalized.indexOf(":");
if (normalized.slice(separatorIndex + 1).startsWith(":")) {
return null;
}

return normalized;
// Share the single model-input parser (alias resolution + gateway preservation +
// format validation) used by the UI and the task tool instead of duplicating it.
return normalizeModelInput(modelInput).model;
}

function parseMultilineCommand(rawInput: string): ParsedMultilineCommand {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4402,15 +4402,17 @@ export const BUILTIN_SKILL_FILES: Record<string, Record<string, string>> = {
"</details>",
"",
"<details>",
"<summary>task (8)</summary>",
"<summary>task (10)</summary>",
"",
"| Env var | JSON path | Type | Description |",
"| ---------------------------------- | ------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |",
"| `MUX_TOOL_INPUT_AGENT_ID` | `agentId` | string | — |",
"| `MUX_TOOL_INPUT_MODEL` | `model` | string | Optional model override for the sub-agent, parsed with the same alias logic as the UI (an alias or a full 'provider:model' string). Omit this unless the user explicitly instructed a specific model — by default the sub-agent inherits the parent's model. Do not assume any particular model is available. |",
"| `MUX_TOOL_INPUT_N` | `n` | number | Optional best-of count. Use n when several agents should try the same prompt independently. Mutually exclusive with variants; omit both for a single task. Only use grouped runs for sub-agents without interfering side effects, such as read-only agents like explore. |",
"| `MUX_TOOL_INPUT_PROMPT` | `prompt` | string | — |",
"| `MUX_TOOL_INPUT_RUN_IN_BACKGROUND` | `run_in_background` | boolean | — |",
"| `MUX_TOOL_INPUT_SUBAGENT_TYPE` | `subagent_type` | string | — |",
"| `MUX_TOOL_INPUT_THINKING` | `thinking` | string | Optional thinking/reasoning-level override for the sub-agent. Accepts a level name (off, low, medium, high, xhigh, max) or a numeric index (resolved against the chosen model). Omit this unless the user explicitly instructed a specific thinking level — by default the sub-agent inherits the parent's thinking level. |",
"| `MUX_TOOL_INPUT_TITLE` | `title` | string | — |",
"| `MUX_TOOL_INPUT_VARIANTS_<INDEX>` | `variants[<INDEX>]` | string | Optional labels for sibling runs of the same prompt template. Use variants when the task should be repeated across labeled lanes such as issue numbers, commit windows, or frontend/backend/tests/docs review lanes. Mutually exclusive with n. When provided, Mux launches one sibling per label and substitutes ${variant} in the prompt. |",
"| `MUX_TOOL_INPUT_VARIANTS_COUNT` | `variants.length` | number | Number of elements in variants (Optional labels for sibling runs of the same prompt template. Use variants when the task should be repeated across labeled lanes such as issue numbers, commit windows, or frontend/backend/tests/docs review lanes. Mutually exclusive with n. When provided, Mux launches one sibling per label and substitutes ${variant} in the prompt.) |",
Expand Down
53 changes: 53 additions & 0 deletions src/node/services/taskService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,59 @@ describe("TaskService", () => {
expect(childEntry?.taskThinkingLevel).toBe("xhigh");
}, 20_000);

test("resolves a numeric thinking override against the inherited model's policy", async () => {
const config = await createTestConfig(rootDir);
stubStableIds(config, ["aaaaaaaaaa"], "bbbbbbbbbb");

const projectPath = await createTestProject(rootDir, "repo", { initGit: false });

const parentId = "1111111111";
await saveWorkspaces(
config,
projectPath,
[
{
path: projectPath,
id: parentId,
name: "parent",
createdAt: new Date().toISOString(),
runtimeConfig: { type: "local" },
// opus-4-6 allows [off, low, medium, high, xhigh]; index 9 clamps to the highest (xhigh).
aiSettings: { model: "anthropic:claude-opus-4-6", thinkingLevel: "off" },
},
],
testTaskSettings()
);

const { workspaceService, sendMessage } = createWorkspaceServiceMocks();
const { taskService } = createTaskServiceHarness(config, { workspaceService });

const created = await createAgentTask(taskService, parentId, "run with numeric thinking", {
thinkingLevel: 9,
});
expect(created.success).toBe(true);
if (!created.success) return;

expect(sendMessage).toHaveBeenCalledWith(
created.data.taskId,
"run with numeric thinking",
{
model: "anthropic:claude-opus-4-6",
agentId: "explore",
thinkingLevel: "xhigh",
experiments: undefined,
},
{ agentInitiated: true }
);

const postCfg = config.loadConfigOrDefault();
const childEntry = Array.from(postCfg.projects.values())
.flatMap((p) => p.workspaces)
.find((w) => w.id === created.data.taskId);
expect(childEntry?.taskModelString).toBe("anthropic:claude-opus-4-6");
expect(childEntry?.taskThinkingLevel).toBe("xhigh");
}, 20_000);

test("agentAiDefaults outrank workspace aiSettingsByAgent for same agent", async () => {
const config = await createTestConfig(rootDir);
stubStableIds(config, ["aaaaaaaaaa"], "bbbbbbbbbb");
Expand Down
Loading
Loading