SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
68d7816 verified
Raw History Blame Contribute Delete
16.3 kB
import { z } from 'zod';
import { Error2, ErrorCodes, isError2 } from '#/errors';
import { isPlainObject } from '#/app/config/toml';
import {
type EnvBindings,
envBindings,
stripEnvBoundFields,
type IConfigService,
} from '#/app/config/config';
import { registerConfigSection } from '#/app/config/configSectionContributions';
import { THINKING_SECTION } from '#/app/kosongConfig/configSection';
import type { IModelCatalog, Model } from '#/llm-adapter/model/catalog';
import {
declaredDefaultEffortForModel,
modelSupportsThinking,
modelSupportsThinkingEffort,
normalizeRequestedThinkingEffort,
type ThinkingConfig,
} from '#/llm-adapter/model/thinking';
export const SUBAGENT_SECTION = 'subagent';
export const SECONDARY_MODEL_SECTION = 'secondaryModel';
export const SubagentConfigSchema = z.object({
timeoutMs: z.number().int().min(0).optional(),
});
export type SubagentConfig = z.infer<typeof SubagentConfigSchema>;
export const SecondaryModelConfigSchema = z.object({
defaultModel: z.string().min(1).optional(),
models: z.record(z.string(), z.string()).optional(),
force: z.boolean().optional(),
model: z.string().min(1).optional(),
maxContextSize: z.number().int().min(1).optional(),
maxInputSize: z.number().int().min(1).optional(),
maxOutputSize: z.number().int().min(1).optional(),
capabilities: z.array(z.string()).optional(),
displayName: z.string().optional(),
reasoningKey: z.string().optional(),
adaptiveThinking: z.boolean().optional(),
supportEfforts: z.array(z.string()).optional(),
defaultEffort: z.string().optional(),
offEffort: z.string().optional(),
});
export type SecondaryModelConfig = z.infer<typeof SecondaryModelConfigSchema>;
export const DEFAULT_SUBAGENT_TIMEOUT_MS = 2 * 60 * 60 * 1000;
export const SUBAGENT_TIMEOUT_ENV = 'KIMI_SUBAGENT_TIMEOUT_MS';
function parseTimeoutMsEnv(raw: string): number | undefined {
const parsed = Number(raw);
return Number.isInteger(parsed) && parsed >= 1 ? parsed : undefined;
}
export const subagentEnvBindings: EnvBindings<SubagentConfig> = envBindings(
SubagentConfigSchema,
{
timeoutMs: { env: SUBAGENT_TIMEOUT_ENV, parse: parseTimeoutMsEnv },
},
);
export const stripSubagentEnv = stripEnvBoundFields(subagentEnvBindings);
registerConfigSection(SUBAGENT_SECTION, SubagentConfigSchema, {
defaultValue: { timeoutMs: DEFAULT_SUBAGENT_TIMEOUT_MS },
env: subagentEnvBindings,
stripEnv: stripSubagentEnv,
});
registerConfigSection(SECONDARY_MODEL_SECTION, SecondaryModelConfigSchema);
export function resolveSubagentTimeoutMs(config: IConfigService): number {
return (
config.get<SubagentConfig | undefined>(SUBAGENT_SECTION)?.timeoutMs ??
DEFAULT_SUBAGENT_TIMEOUT_MS
);
}
export const PRIMARY_SUBAGENT_MODEL_CHOICE = 'primary';
export interface SubagentModelPool {
readonly defaultModel?: string;
readonly models: Record<string, string>;
}
export function resolveSubagentModelPool(config: IConfigService): SubagentModelPool | undefined {
const section = config.get<SecondaryModelConfig | undefined>(SECONDARY_MODEL_SECTION);
if (section?.models !== undefined) {
return { defaultModel: section.defaultModel, models: section.models };
}
if (section?.defaultModel !== undefined) {
return { defaultModel: section.defaultModel, models: { [section.defaultModel]: '' } };
}
if (section?.model !== undefined) {
return { defaultModel: section.model, models: { [section.model]: '' } };
}
return undefined;
}
export const SECONDARY_MODEL_FORCE_REQUIRES_DEFAULT_MESSAGE =
'[secondary_model].default_model is required when [secondary_model].force is set';
export const SECONDARY_MODEL_FORCE_EXCLUDES_MODELS_MESSAGE =
'[secondary_model].force cannot be combined with [secondary_model.models]: the pool table only exists to offer the main agent a choice, and force removes that choice';
export function isSubagentModelForced(config: IConfigService): boolean {
return config.get<SecondaryModelConfig | undefined>(SECONDARY_MODEL_SECTION)?.force === true;
}
export function exposesSubagentModelChoice(config: IConfigService): boolean {
if (isSubagentModelForced(config)) return false;
return resolveSubagentModelPool(config) !== undefined;
}
export const SECONDARY_MODEL_DEFAULT_MODEL_REQUIRED_MESSAGE =
'[secondary_model].default_model is required when [secondary_model.models] is configured';
export const SECONDARY_MODEL_PRIMARY_MODEL_RESERVED_MESSAGE = `[secondary_model.models] key "${PRIMARY_SUBAGENT_MODEL_CHOICE}" is reserved: it always binds the caller's own model. Rename the pool entry.`;
export function assertValidSubagentModelPool(
pool: SubagentModelPool,
modelCatalog: IModelCatalog,
): void {
if (Object.hasOwn(pool.models, PRIMARY_SUBAGENT_MODEL_CHOICE)) {
throw new Error2(ErrorCodes.CONFIG_INVALID, SECONDARY_MODEL_PRIMARY_MODEL_RESERVED_MESSAGE, {
details: {
section: SECONDARY_MODEL_SECTION,
field: 'models',
model: PRIMARY_SUBAGENT_MODEL_CHOICE,
},
});
}
const aliases = Object.keys(pool.models);
if (pool.defaultModel === undefined) {
throw new Error2(ErrorCodes.CONFIG_INVALID, SECONDARY_MODEL_DEFAULT_MODEL_REQUIRED_MESSAGE, {
details: { section: SECONDARY_MODEL_SECTION, field: 'defaultModel' },
});
}
if (!Object.hasOwn(pool.models, pool.defaultModel)) {
throw new Error2(
ErrorCodes.CONFIG_INVALID,
`[secondary_model].default_model "${pool.defaultModel}" is not a [secondary_model.models] key. Available models: ${aliases.join(', ')}.`,
{ details: { model: pool.defaultModel, availableModels: aliases } },
);
}
for (const alias of aliases) {
try {
modelCatalog.get(alias);
} catch (error) {
throw new Error2(
ErrorCodes.CONFIG_INVALID,
`[secondary_model.models] entry "${alias}" could not be resolved: ${error instanceof Error ? error.message : String(error)}`,
{ cause: error, details: { model: alias } },
);
}
}
}
export function assertValidSubagentModelConfig(
config: IConfigService,
modelCatalog: IModelCatalog,
): void {
const section = config.get<SecondaryModelConfig | undefined>(SECONDARY_MODEL_SECTION);
if (section?.force === true) {
if (section.models !== undefined) {
throw new Error2(ErrorCodes.CONFIG_INVALID, SECONDARY_MODEL_FORCE_EXCLUDES_MODELS_MESSAGE, {
details: { section: SECONDARY_MODEL_SECTION, field: 'force' },
});
}
if (section.defaultModel === undefined && section.model === undefined) {
throw new Error2(ErrorCodes.CONFIG_INVALID, SECONDARY_MODEL_FORCE_REQUIRES_DEFAULT_MESSAGE, {
details: { section: SECONDARY_MODEL_SECTION, field: 'defaultModel' },
});
}
}
const pool = resolveSubagentModelPool(config);
if (pool !== undefined) assertValidSubagentModelPool(pool, modelCatalog);
assertValidSubagentDefaultEffort(section, pool, modelCatalog);
}
function assertValidSubagentDefaultEffort(
section: SecondaryModelConfig | undefined,
pool: SubagentModelPool | undefined,
modelCatalog: IModelCatalog,
): void {
const effort =
section?.defaultEffort === undefined
? undefined
: normalizeRequestedThinkingEffort(section.defaultEffort);
if (effort === undefined || pool === undefined) return;
for (const alias of Object.keys(pool.models)) {
const model = modelCatalog.get(alias);
if (effort === 'off' && model.alwaysThinking === true) {
throw new Error2(
ErrorCodes.CONFIG_INVALID,
`[secondary_model].default_effort "off" cannot disable thinking for model "${alias}", which always reasons. Choose a concrete thinking effort instead of "off".`,
{
details: {
section: SECONDARY_MODEL_SECTION,
field: 'defaultEffort',
model: alias,
effort,
},
},
);
}
if (modelSupportsThinkingEffort(effort, model, true)) continue;
if (!modelSupportsThinking(model)) {
throw new Error2(
ErrorCodes.CONFIG_INVALID,
`[secondary_model].default_effort "${effort}" is set but model "${alias}" does not support thinking.`,
{
details: {
section: SECONDARY_MODEL_SECTION,
field: 'defaultEffort',
model: alias,
effort,
},
},
);
}
throw new Error2(
ErrorCodes.CONFIG_INVALID,
`[secondary_model].default_effort "${effort}" is not supported by model "${alias}". Supported efforts: ${model.supportEfforts?.join(', ')}.`,
{
details: {
section: SECONDARY_MODEL_SECTION,
field: 'defaultEffort',
model: alias,
effort,
},
},
);
}
}
export type SubagentModelSource = 'forced' | 'primary_override' | 'inherited' | 'secondary_pool';
export function resolveSubagentBinding(
config: IConfigService,
own: { modelAlias: string; thinkingLevel: string },
requested?: string,
): { model: string; thinking?: string; modelSource: SubagentModelSource } {
const section = config.get<SecondaryModelConfig | undefined>(SECONDARY_MODEL_SECTION);
if (section?.force === true) {
if (section.models !== undefined) {
throw new Error2(ErrorCodes.CONFIG_INVALID, SECONDARY_MODEL_FORCE_EXCLUDES_MODELS_MESSAGE, {
details: { section: SECONDARY_MODEL_SECTION, field: 'force' },
});
}
const forcedModel = section.defaultModel ?? section.model;
if (forcedModel === undefined) {
throw new Error2(ErrorCodes.CONFIG_INVALID, SECONDARY_MODEL_FORCE_REQUIRES_DEFAULT_MESSAGE, {
details: { section: SECONDARY_MODEL_SECTION, field: 'defaultModel' },
});
}
if (requested !== undefined) {
throw new Error2(
ErrorCodes.CONFIG_INVALID,
`Invalid model "${requested}": [secondary_model].force is set, so every subagent binds "${forcedModel}" (omit the model parameter).`,
{ details: { model: requested } },
);
}
return { model: forcedModel, thinking: section.defaultEffort, modelSource: 'forced' };
}
if (requested === PRIMARY_SUBAGENT_MODEL_CHOICE) {
return { model: own.modelAlias, thinking: own.thinkingLevel, modelSource: 'primary_override' };
}
const pool = resolveSubagentModelPool(config);
if (pool === undefined) {
if (requested !== undefined) {
throw new Error2(
ErrorCodes.CONFIG_INVALID,
`Invalid model "${requested}": no [secondary_model.models] pool is configured, so subagents inherit the caller's model (pass "primary" or omit the model parameter).`,
{ details: { model: requested } },
);
}
return { model: own.modelAlias, thinking: own.thinkingLevel, modelSource: 'inherited' };
}
if (Object.hasOwn(pool.models, PRIMARY_SUBAGENT_MODEL_CHOICE)) {
throw new Error2(ErrorCodes.CONFIG_INVALID, SECONDARY_MODEL_PRIMARY_MODEL_RESERVED_MESSAGE, {
details: {
section: SECONDARY_MODEL_SECTION,
field: 'models',
model: PRIMARY_SUBAGENT_MODEL_CHOICE,
},
});
}
const choice = requested ?? pool.defaultModel;
if (choice === undefined) {
throw new Error2(ErrorCodes.CONFIG_INVALID, SECONDARY_MODEL_DEFAULT_MODEL_REQUIRED_MESSAGE, {
details: { section: SECONDARY_MODEL_SECTION, field: 'defaultModel' },
});
}
if (!Object.hasOwn(pool.models, choice)) {
const available = [...Object.keys(pool.models), PRIMARY_SUBAGENT_MODEL_CHOICE];
throw new Error2(
ErrorCodes.CONFIG_INVALID,
`Invalid model "${choice}". Available models: ${available.join(', ')}.`,
{ details: { model: choice, availableModels: available } },
);
}
return { model: choice, thinking: section?.defaultEffort, modelSource: 'secondary_pool' };
}
export function resolveSubagentThinking(
config: IConfigService,
model: Model | undefined,
explicit: string | undefined,
): string | undefined {
if (explicit !== undefined) return explicit;
if (config.get<ThinkingConfig>(THINKING_SECTION)?.enabled === false) return undefined;
return declaredDefaultEffortForModel(model);
}
export function buildSubagentModelDescriptions(
config: IConfigService,
callerModelAlias: string | undefined,
): string | undefined {
if (!exposesSubagentModelChoice(config)) return undefined;
const pool = resolveSubagentModelPool(config)!;
const lines = ['Available models (pass via model):'];
const defaultModel = pool.defaultModel;
for (const alias of orderedPoolAliases(pool)) {
const marker = alias === defaultModel ? ' [default]' : '';
lines.push(formatPoolLine(`${alias}${marker}`, pool.models[alias]!));
}
const primaryLabel =
callerModelAlias === undefined
? PRIMARY_SUBAGENT_MODEL_CHOICE
: `${PRIMARY_SUBAGENT_MODEL_CHOICE} (= ${callerModelAlias})`;
lines.push(
`- ${primaryLabel}: your current model and thinking level`,
);
lines.push("Pool entries don't inherit your thinking level.");
return lines.join('\n');
}
export function buildSubagentModelSummary(config: IConfigService): string | undefined {
if (!exposesSubagentModelChoice(config)) return undefined;
const pool = resolveSubagentModelPool(config)!;
const labels = orderedPoolAliases(pool).map((alias) =>
alias === pool.defaultModel ? `${alias} [default]` : alias,
);
labels.push(`${PRIMARY_SUBAGENT_MODEL_CHOICE} (your current model and thinking level)`);
return `Available models (pass via model): ${labels.join(', ')}.`;
}
function orderedPoolAliases(pool: SubagentModelPool): string[] {
const aliases = Object.keys(pool.models);
const defaultModel = pool.defaultModel;
if (defaultModel === undefined || !Object.hasOwn(pool.models, defaultModel)) return aliases;
return [defaultModel, ...aliases.filter((alias) => alias !== defaultModel)];
}
function formatPoolLine(label: string, description: string): string {
return description === '' ? `- ${label}` : `- ${label}: ${description}`;
}
export function stripSubagentModelParameter(
parameters: Record<string, unknown>,
): Record<string, unknown> {
const properties = parameters['properties'];
if (!isPlainObject(properties) || !('model' in properties)) return parameters;
const nextProperties = { ...properties };
delete nextProperties['model'];
const next: Record<string, unknown> = { ...parameters, properties: nextProperties };
const required = parameters['required'];
if (Array.isArray(required) && required.includes('model')) {
next['required'] = required.filter((entry) => entry !== 'model');
}
return next;
}
export function stripSubagentForkParameter(
parameters: Record<string, unknown>,
): Record<string, unknown> {
const properties = parameters['properties'];
if (!isPlainObject(properties) || !('fork' in properties)) return parameters;
const nextProperties = { ...properties };
delete nextProperties['fork'];
const next: Record<string, unknown> = { ...parameters, properties: nextProperties };
const required = parameters['required'];
if (Array.isArray(required) && required.includes('fork')) {
next['required'] = required.filter((entry) => entry !== 'fork');
}
return next;
}
export function wrapSubagentModelError(
error: unknown,
boundModel: string,
callerModelAlias: string | undefined,
): unknown {
if (boundModel === callerModelAlias) return error;
if (!isError2(error) || error.code !== ErrorCodes.CONFIG_INVALID) return error;
if (error.details?.['model'] !== boundModel) return error;
return new Error2(
error.code,
`${error.message} (subagent model "${boundModel}" comes from [secondary_model.models] — check that it names a valid [models] entry)`,
{
cause: error,
name: error.name,
details: {
...error.details,
subagentModel: boundModel,
subagentModelConfig: {
section: 'secondary_model.models',
},
},
},
);
}
export function formatSubagentTimeoutDescription(ms: number): string {
if (ms % (60 * 60 * 1000) === 0) {
const h = ms / (60 * 60 * 1000);
return `${h} hour${h === 1 ? '' : 's'}`;
}
if (ms % (60 * 1000) === 0) {
const m = ms / (60 * 1000);
return `${m} minute${m === 1 ? '' : 's'}`;
}
if (ms % 1000 === 0) {
const s = ms / 1000;
return `${s} second${s === 1 ? '' : 's'}`;
}
return `${ms} ms`;
}