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
75 changes: 69 additions & 6 deletions src/cli/config-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ import {
type CliConfigOverrides,
type CliProviderPreset,
type ConfigFile,
parseReasoningBudget,
REASONING_BUDGET_VALUES,
} from './config.js';
import { errorMessage } from '../errors.js';
import { isZhLocale, parseLanguageSetting, uiText } from './cli-locale.js';
import { guessModelProvider, print, renderAuthStatus, sanitizeBaseUrl } from './setup-wizard.js';
import { withoutSecret } from './config-snapshot.js';
import { parsePermissionRuleSpec } from './permission-rules.js';
import { parseCliInteractionMode } from './interaction-mode.js';
import { untrustedFolderLine } from './workspace-trust.js';

function serializeResolvedConfig(
resolved: ReturnType<typeof resolveCliConfig>
Expand Down Expand Up @@ -75,6 +78,8 @@ function serializeResolvedConfig(
maxAgentTurnsSource: resolved.maxAgentTurnsSource,
contextTokens: resolved.contextTokens,
contextTokensSource: resolved.contextTokensSource,
reasoningBudget: resolved.reasoningBudget ?? 'adaptive',
reasoningBudgetSource: resolved.reasoningBudgetSource ?? 'default',
compactionSettings: { ...resolved.compactionSettings },
compactionSettingsSource: resolved.compactionSettingsSource,
configWarnings: auditResolvedCliConfig(resolved),
Expand Down Expand Up @@ -378,7 +383,7 @@ export const MOSS_ENV_REFERENCE: ReadonlyArray<{ group: string; vars: readonly s
group: 'providers, models & fallback',
vars: [
'MOSS_BEST_OF_N',
'MOSS_REASONING_BUDGET',
'MOSS_REASONING_BUDGET (off|adaptive|high; overrides config agent.reasoningBudget)',
'MOSS_MODEL_BALANCED',
'MOSS_MODEL_CHEAP',
'MOSS_MODEL_STRONG',
Expand Down Expand Up @@ -476,6 +481,8 @@ const ENV_GROUP_ZH: Record<string, string> = {
};

const ENV_NOTE_ZH: Record<string, string> = {
'off|adaptive|high; overrides config agent.reasoningBudget':
'off|adaptive|high;优先于配置 agent.reasoningBudget',
'process env only; a project .env cannot set this': '仅进程环境;项目 .env 不能设置',
'process env or --config-file; a project .env cannot set this':
'进程环境或 --config-file;项目 .env 不能设置',
Expand Down Expand Up @@ -596,6 +603,13 @@ export function renderConfigHelp(zh = false): string {
' moss config set guardrails.input.redactPatterns SECRET=[^\\\\s]+',
' moss config set agent.maxTurns 96',
' moss config set agent.contextTokens 200000',
' # agent.reasoningBudget — Moss adaptive-reasoning knob (off|adaptive|high).',
' # high forces reasoning=high on outbound calls; adaptive may escalate to high under',
" # verify/turn-error pressure; off means do NOT escalate (keeps the model's own",
' # reasoning level — it does not disable provider-native thinking).',
' # Higher budgets spend more tokens, take longer, and cost more.',
' # An untrusted project can only lower it; a raise is ignored with a notice.',
' moss config set agent.reasoningBudget high # off|adaptive|high',
' moss config set agent.compaction.reserveTokens 20000',
' moss config set rdkDocs false',
' moss config set rdkDocs.package ../rdk-docs-mcp',
Expand Down Expand Up @@ -649,6 +663,13 @@ function renderConfigHelpZh(): string {
' `moss config set baseUrl https://your-gateway.example` # /v1 可写可不写(https://主机/v1 也可以);不必带 /chat/completions',
' `moss config set rdkDocs false`',
' `moss config set rdkDocs.package ../rdk-docs-mcp`',
' `moss config set agent.maxTurns 96`',
' `moss config set agent.contextTokens 200000`',
' # agent.reasoningBudget — Moss 自适应推理档位(可选值 off|adaptive|high)。',
' # high:出站推理档固定为 high;adaptive:验证失败或回合出错时可能升到 high;',
' # off:不升档,保留模型自身推理档(不是关掉模型自带思考)。越高越慢、费用越高。',
' # 未信任的项目只能把它调低;调高会被忽略并提示。',
' `moss config set agent.reasoningBudget high` # 可选值:off|adaptive|high',
' `moss config set language auto|en|zh` # 只写用户配置(不能 --project,项目 .env 也不能设置)',
' # 界面语言优先级:--lang > MOSS_LANG > `language` > 系统区域。auto 仅在区域以 zh 开头时用中文。',
].join('\n');
Expand All @@ -659,7 +680,15 @@ export function runConfigShow(
options: { json?: boolean; overrides?: CliConfigOverrides } = {}
): void {
const overrides = options.overrides ?? {};
// Tip when an untrusted project tried to set routing fields (incl. reasoningBudget).
const loaded = loadCliConfigFile(process.env, process.argv.slice(2), startDir);
const ignored = loaded.ignoredProjectRouting ?? [];
if (ignored.length > 0) {
const line = untrustedFolderLine(ignored, isZhLocale());
if (line) console.error(line);
}
if (options.json) {
// Pass undefined so renderConfigJson reloads with fieldSources intact.
standardOutput.write(`${renderConfigJson(undefined, process.env, startDir, overrides)}\n`);
return;
}
Expand Down Expand Up @@ -901,8 +930,8 @@ function buildProjectConfigTemplate(): ConfigFile {

function supportedConfigKeys(): string {
return uiText(
'Supported keys — model: provider, model, baseUrl, apiKey, apiKeyEnv; operational: profile, workspace, safetyMode, approvalPolicy, trustedTools, deniedTools, permissions.defaultMode, permissions.allow, permissions.ask, permissions.deny, permissions.deviceTrust, permissions.trustedDevices, rdkDocs, rdkDocs.enabled, rdkDocs.package, promptCache, promptCacheDebug, guardrails.input.blockPatterns, guardrails.input.redactPatterns, guardrails.output.blockPatterns, guardrails.output.redactPatterns, agent.maxTurns, agent.contextTokens, agent.compaction.reserveTokens, agent.compaction.keepRecentTokens, language',
'支持的键:provider、model、baseUrl、apiKey、apiKeyEnv、profile、workspace、safetyMode、approvalPolicy、trustedTools、deniedTools、permissions.defaultMode、permissions.allow、permissions.ask、permissions.deny、permissions.deviceTrust、permissions.trustedDevices、rdkDocs、rdkDocs.enabled、rdkDocs.package、promptCache、promptCacheDebug、guardrails.input.blockPatterns、guardrails.input.redactPatterns、guardrails.output.blockPatterns、guardrails.output.redactPatterns、agent.maxTurns、agent.contextTokens、agent.compaction.reserveTokens、agent.compaction.keepRecentTokens、language'
'Supported keys — model: provider, model, baseUrl, apiKey, apiKeyEnv; operational: profile, workspace, safetyMode, approvalPolicy, trustedTools, deniedTools, permissions.defaultMode, permissions.allow, permissions.ask, permissions.deny, permissions.deviceTrust, permissions.trustedDevices, rdkDocs, rdkDocs.enabled, rdkDocs.package, promptCache, promptCacheDebug, guardrails.input.blockPatterns, guardrails.input.redactPatterns, guardrails.output.blockPatterns, guardrails.output.redactPatterns, agent.maxTurns, agent.contextTokens, agent.reasoningBudget, agent.compaction.reserveTokens, agent.compaction.keepRecentTokens, language',
'支持的键:provider、model、baseUrl、apiKey、apiKeyEnv、profile、workspace、safetyMode、approvalPolicy、trustedTools、deniedTools、permissions.defaultMode、permissions.allow、permissions.ask、permissions.deny、permissions.deviceTrust、permissions.trustedDevices、rdkDocs、rdkDocs.enabled、rdkDocs.package、promptCache、promptCacheDebug、guardrails.input.blockPatterns、guardrails.input.redactPatterns、guardrails.output.blockPatterns、guardrails.output.redactPatterns、agent.maxTurns、agent.contextTokens、agent.reasoningBudget、agent.compaction.reserveTokens、agent.compaction.keepRecentTokens、language'
);
}

Expand Down Expand Up @@ -1260,6 +1289,29 @@ function applyConfigSetPair(
} catch (err) {
return { ok: false, messages: [errorMessage(err)] };
}
} else if (key === 'agent.reasoningBudget') {
if (!value.trim()) {
return {
ok: false,
messages: [
isZhLocale()
? `agent.reasoningBudget:值不能为空。可选值:${REASONING_BUDGET_VALUES.join('、')}`
: `agent.reasoningBudget: value must not be empty. Supported values: ${REASONING_BUDGET_VALUES.join(', ')}`,
],
};
}
const budget = parseReasoningBudget(value);
if (!budget) {
return {
ok: false,
messages: [
isZhLocale()
? `agent.reasoningBudget 取值无效:${value}。可选值:${REASONING_BUDGET_VALUES.join('、')}`
: `Unsupported agent.reasoningBudget value: ${value}. Supported values: ${REASONING_BUDGET_VALUES.join(', ')}`,
],
};
}
next.agent = { ...next.agent, reasoningBudget: budget };
} else if (key === 'agent.maxTurns' || key === 'agent.contextTokens') {
const parsed = parseConfigPositiveInteger(value, key);
if (!parsed.ok) {
Expand Down Expand Up @@ -1322,9 +1374,17 @@ export function runConfigSet(args: string[], startDir = process.cwd()): void {
return;
}
if (!value) {
print(
`config ${key}: value must not be empty. Run \`moss config --help\` for supported keys and usage.`
);
if (key === 'agent.reasoningBudget') {
print(
isZhLocale()
? `config agent.reasoningBudget:值不能为空。可选值:${REASONING_BUDGET_VALUES.join('、')}`
: `config agent.reasoningBudget: value must not be empty. Supported values: ${REASONING_BUDGET_VALUES.join(', ')}`
);
} else {
print(
`config ${key}: value must not be empty. Run \`moss config --help\` for supported keys and usage.`
);
}
process.exitCode = 1;
return;
}
Expand Down Expand Up @@ -1485,6 +1545,9 @@ export function runConfigUnset(args: string[], startDir = process.cwd()): void {
} else if (key === 'agent.contextTokens') {
next.agent = { ...current.agent };
delete next.agent.contextTokens;
} else if (key === 'agent.reasoningBudget') {
next.agent = { ...current.agent };
delete next.agent.reasoningBudget;
} else if (key === 'agent.compaction.reserveTokens') {
next.agent = { ...current.agent, compaction: { ...current.agent?.compaction } };
delete next.agent.compaction?.reserveTokens;
Expand Down
6 changes: 6 additions & 0 deletions src/cli/config-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const ZH_FIELD: Record<string, string> = {
guardrails: '护栏',
'max turns': '最大轮次',
'context tokens': '上下文词元',
'reasoning budget': '推理强度',
'max output': '最大输出',
compaction: '压缩',
'model tiers': '模型分级',
Expand Down Expand Up @@ -276,6 +277,11 @@ const FIELDS = {
'context tokens',
(c: ResolvedCliConfig) => `${c.contextTokens} ${sourceNote(c.contextTokensSource)}`,
],
reasoningBudget: [
'reasoning budget',
(c: ResolvedCliConfig) =>
`${c.reasoningBudget ?? 'adaptive'} ${sourceNote(c.reasoningBudgetSource ?? 'default')}`,
],
maxOutput: [
'max output',
(c: ResolvedCliConfig) =>
Expand Down
105 changes: 95 additions & 10 deletions src/cli/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import {
recordDotenvOrigin,
} from '../utils/startup-env.js';
import { officialEnvOffers } from './env-credentials.js';
import { setupCopy } from './cli-locale.js';
import { isZhLocale, setupCopy } from './cli-locale.js';
import { isDeviceTrustEnv, parseDeviceTrustList } from '../safety/device-trust.js';
import type { PricingConfig } from './model-pricing.js';
import type { StatusLineConfig } from './status-line.js';
Expand Down Expand Up @@ -660,6 +660,30 @@ function mergeModelOutputConfigs(
return Object.keys(merged).length > 0 ? merged : undefined;
}

export const REASONING_BUDGET_VALUES = ['off', 'adaptive', 'high'] as const;
export type ReasoningBudget = (typeof REASONING_BUDGET_VALUES)[number];

export function parseReasoningBudget(value: unknown): ReasoningBudget | undefined {
if (typeof value !== 'string') return undefined;
const normalized = value.toLowerCase().trim();
return (REASONING_BUDGET_VALUES as readonly string[]).includes(normalized)
? (normalized as ReasoningBudget)
: undefined;
}

const warnedInvalidReasoningBudgetEnv = new Set<string>();

/** MOSS_LANG-style: warn once per process for an invalid MOSS_REASONING_BUDGET. */
function warnInvalidReasoningBudgetEnv(raw: string): void {
if (warnedInvalidReasoningBudgetEnv.has(raw)) return;
warnedInvalidReasoningBudgetEnv.add(raw);
const zh = isZhLocale();
const msg = zh
? `[moss] MOSS_REASONING_BUDGET 只能是 off、adaptive 或 high,收到「${raw}」,已回退到配置或默认 adaptive。`
: `[moss] MOSS_REASONING_BUDGET must be off|adaptive|high, got "${raw}"; falling back to config or default adaptive.`;
console.error(msg);
}

function mergeAgentRuntimeConfig(
userAgent: ConfigFile['agent'],
projectAgent: ConfigFile['agent']
Expand Down Expand Up @@ -715,6 +739,26 @@ export function listProjectRoutingConfigFields(project: ConfigFile): string[] {
return ignored;
}

const REASONING_BUDGET_RANK: Record<ReasoningBudget, number> = { off: 0, adaptive: 1, high: 2 };

/**
* An untrusted project may only lower reasoning intensity (off < adaptive <
* high). The user's effective value is the user config value, or the
* runtime default `adaptive` when unset. Same shape as projectProfileLoosens.
*/
function projectReasoningBudgetRaises(user: ConfigFile, project: ConfigFile): boolean {
const projectBudget = parseReasoningBudget(project.agent?.reasoningBudget);
if (!projectBudget) return false;
const userBudget = parseReasoningBudget(user.agent?.reasoningBudget) ?? 'adaptive';
return REASONING_BUDGET_RANK[projectBudget] > REASONING_BUDGET_RANK[userBudget];
}

function omitRaisingProjectReasoningBudget(user: ConfigFile, project: ConfigFile): ConfigFile {
if (!project.agent || !projectReasoningBudgetRaises(user, project)) return project;
const { reasoningBudget: _rb, ...rest } = project.agent;
return { ...project, agent: Object.keys(rest).length > 0 ? rest : undefined };
}

function omitProjectRoutingConfig(project: ConfigFile): ConfigFile {
const next: ConfigFile = { ...project };
for (const key of PROJECT_ROUTING_CONFIG_KEYS) delete next[key];
Expand Down Expand Up @@ -858,6 +902,11 @@ export function fieldSourcesFor(
mark('promptCache', user.promptCache !== undefined, project.promptCache !== undefined, true);
mark('guardrails', user.guardrails !== undefined, project.guardrails !== undefined, true);
mark('agent.maxTurns', user.agent?.maxTurns !== undefined, project.agent?.maxTurns !== undefined);
mark(
'agent.reasoningBudget',
user.agent?.reasoningBudget !== undefined,
project.agent?.reasoningBudget !== undefined
);
mark(
'agent.contextTokens',
user.agent?.contextTokens !== undefined,
Expand Down Expand Up @@ -1216,12 +1265,18 @@ export function loadCliConfigFile(
const rawProject = loadConfigFile(projectConfigPath);
const trusted = projectRoutingTrusted(env, argv, startDir, options?.trustProjectRouting);
const ignoredProjectRouting = trusted ? [] : listProjectRoutingConfigFields(rawProject);
if (!trusted && projectReasoningBudgetRaises(userConfig, rawProject)) {
ignoredProjectRouting.push('reasoningBudget');
}
const droppedProjectPermissions = trusted
? []
: listDroppedProjectPermissionFields(userConfig, rawProject);
const projectConfig = trusted
? rawProject
: omitLooseningProjectPermissions(userConfig, omitProjectRoutingConfig(rawProject));
: omitRaisingProjectReasoningBudget(
userConfig,
omitLooseningProjectPermissions(userConfig, omitProjectRoutingConfig(rawProject))
);
const merged = mergeConfigFiles(projectConfig, userConfig, options);
const sources = fieldSourcesFor(userConfig, projectConfig);
const keyLayer =
Expand Down Expand Up @@ -1535,6 +1590,9 @@ export interface ResolvedCliConfig {
bestOfN?: number;
/** v0.10 W4 (config agent.reasoningBudget + MOSS_REASONING_BUDGET env). */
reasoningBudget?: 'off' | 'adaptive' | 'high';
reasoningBudgetSource?: string;
/** Set when MOSS_REASONING_BUDGET was present but not off|adaptive|high. */
invalidReasoningBudgetEnv?: string;
/** v0.12 (agent.modelTiers + MOSS_MODEL_CHEAP/BALANCED/STRONG env). */
modelTiers?: { cheap?: string; balanced?: string; strong?: string };
modelTiersSource?: string;
Expand Down Expand Up @@ -1605,9 +1663,21 @@ export function auditResolvedCliConfig(
| 'deniedTools'
| 'deniedToolsSource'
| 'permissions'
>
> &
Partial<Pick<ResolvedCliConfig, 'invalidReasoningBudgetEnv'>>
): CliConfigAuditWarning[] {
const warnings: CliConfigAuditWarning[] = [];
if (config.invalidReasoningBudgetEnv) {
warnings.push({
code: 'reasoningBudget.env_invalid',
severity: 'warn',
source: 'MOSS_REASONING_BUDGET',
message: uiText(
`MOSS_REASONING_BUDGET "${config.invalidReasoningBudgetEnv}" is not off|adaptive|high; using config or default adaptive`,
`MOSS_REASONING_BUDGET「${config.invalidReasoningBudgetEnv}」不是 off|adaptive|high;已回退到配置或默认 adaptive`
),
});
}
// v0.26 audit re-check (PRD W1 告警重校): the old blanket auto-approval
// warning would fire for every factory-default user now that full is the
// default. The warning only fires when the auto-approval stance came from
Expand Down Expand Up @@ -2411,13 +2481,26 @@ export function resolveCliConfig(
apiKeyFromProjectEnv = true;
}
}
const rawReasoningBudget = (env.MOSS_REASONING_BUDGET ?? '').toLowerCase().trim();
const reasoningBudget =
rawReasoningBudget === 'off' ||
rawReasoningBudget === 'adaptive' ||
rawReasoningBudget === 'high'
? rawReasoningBudget
: activeConfig.agent?.reasoningBudget;
// Same precedence as agent.maxTurns / agent.contextTokens: a valid
// MOSS_REASONING_BUDGET env wins over config agent.reasoningBudget
// (project layer over user layer), then the runtime default (adaptive).
const rawReasoningBudgetEnv = env.MOSS_REASONING_BUDGET;
const envReasoningBudget = parseReasoningBudget(rawReasoningBudgetEnv);
const invalidReasoningBudgetEnv =
typeof rawReasoningBudgetEnv === 'string' &&
rawReasoningBudgetEnv.trim() !== '' &&
envReasoningBudget === undefined
? rawReasoningBudgetEnv.trim()
: undefined;
if (invalidReasoningBudgetEnv) warnInvalidReasoningBudgetEnv(invalidReasoningBudgetEnv);
const configReasoningBudget = parseReasoningBudget(activeConfig.agent?.reasoningBudget);
const reasoningBudget = envReasoningBudget ?? configReasoningBudget;
const reasoningBudgetSource =
envReasoningBudget !== undefined
? 'MOSS_REASONING_BUDGET'
: configReasoningBudget !== undefined
? fileLayer('agent.reasoningBudget')
: 'default';
const runBudget =
budgetMaxTokens !== undefined ||
budgetMaxToolCalls !== undefined ||
Expand Down Expand Up @@ -2526,6 +2609,8 @@ export function resolveCliConfig(
...(runBudget ? { budget: runBudget } : {}),
...(bestOfN !== undefined ? { bestOfN } : {}),
...(reasoningBudget ? { reasoningBudget } : {}),
reasoningBudgetSource,
...(invalidReasoningBudgetEnv ? { invalidReasoningBudgetEnv } : {}),
...(hasModelTiers ? { modelTiers, modelTiersSource } : { modelTiersSource }),
compactionSettingsSource,
configPath: configPaths?.configPath ?? resolveConfigPath(undefined, env),
Expand Down
2 changes: 2 additions & 0 deletions src/cli/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ export function renderCliStatus(
'guardrails',
'maxTurns',
'contextTokens',
'reasoningBudget',
'maxOutput',
'compaction',
]),
Expand Down Expand Up @@ -533,6 +534,7 @@ export function renderCliPermissions(
'guardrails',
'maxTurns',
'contextTokens',
'reasoningBudget',
'maxOutput',
'compaction',
'warnings',
Expand Down
1 change: 1 addition & 0 deletions src/cli/setup-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ export function renderAuthStatus(
'guardrails',
'maxTurns',
'contextTokens',
'reasoningBudget',
'compaction',
'modelTiers',
'warnings',
Expand Down
Loading
Loading