feat(models): add background task model override config and runtime wiring

This commit is contained in:
William Valentin
2026-02-19 09:52:45 -08:00
parent 4df1291e64
commit 027f7ad283
6 changed files with 367 additions and 4 deletions
+65 -1
View File
@@ -13,7 +13,7 @@ import { createMediaSendTool, createAgentDelegateTool } from '../tools/index.js'
import type { AgentDelegateDeps } from '../tools/index.js';
import { createSandboxedShellTool, createSandboxedProcessStartTool, SandboxManager } from '../sandbox/index.js';
import { MODEL_PROVIDERS, type Config, type ModelConfig, type ModelProvider } from '../config/index.js';
import { ModelRouter, type ModelTier } from '../models/index.js';
import { ModelRouter, type ModelClient, type ModelTier } from '../models/index.js';
import { ToolRegistry, ToolExecutor } from '../tools/index.js';
import { SessionManager } from '../session/index.js';
import { AgentConfigRegistry, AgentRouter } from '../agents/index.js';
@@ -77,6 +77,55 @@ function tierFromUseCase(config: Config, useCaseRaw: unknown): ModelTier | undef
return undefined;
}
function buildBackgroundModelOverrides(config: Config): Partial<Record<keyof DelegationConfig, {
client: ModelClient;
label: string;
fallbackTier: ModelTier;
}>> {
const overrides: Partial<Record<keyof DelegationConfig, {
client: ModelClient;
label: string;
fallbackTier: ModelTier;
}>> = {};
const configured = config.agents?.background_models ?? {};
const providerConfigs = buildProviderConfigMap(config);
const tasks: Array<keyof DelegationConfig> = [
'compaction',
'memory_extraction',
'classification',
'tool_summarisation',
'complex_reasoning',
];
for (const task of tasks) {
const entry = configured[task];
if (!entry || entry.enabled === false) {
continue;
}
const template = providerConfigs[entry.provider];
try {
const client = createClientFromConfig(
template
? { ...template, provider: entry.provider, model: entry.model }
: { provider: entry.provider, model: entry.model },
);
overrides[task] = {
client,
label: `${entry.provider}/${entry.model}`,
fallbackTier: entry.fallback_tier,
};
} catch (error) {
console.warn(
`[Flynn:routing] Failed to initialize background model override for ${task} ` +
`(${entry.provider}/${entry.model}): ${error instanceof Error ? error.message : String(error)}`,
);
}
}
return overrides;
}
function parseResearchPrefix(text: string): string | undefined {
const trimmed = text.trim();
const researchMatch = trimmed.match(/^research(?:\s*[:,-])?\s+(.+)$/i);
@@ -174,6 +223,7 @@ export function createMessageRouter(deps: {
// Cache agents by session ID + agent config name to avoid recreating on every message
const agents = new Map<string, { orchestrator: AgentOrchestrator; collector: OutboundAttachmentCollector }>();
const talkModeUntil = new Map<string, number>();
const activeRuns = new Map<string, AgentOrchestrator>();
async function maybeBuildTtsAttachment(responseText: string, channel: string) {
if (!isTtsEnabledForChannel(deps.config, channel)) {
@@ -261,6 +311,7 @@ export function createMessageRouter(deps: {
tool_summarisation: deps.config.agents.delegation.tool_summarisation ?? 'fast',
complex_reasoning: deps.config.agents.delegation.complex_reasoning ?? 'complex',
};
const backgroundModelOverrides = buildBackgroundModelOverrides(deps.config);
// Clone the tool registry and replace high-risk tools with sandboxed versions if configured.
let effectiveToolRegistry = deps.toolRegistry;
@@ -377,6 +428,7 @@ export function createMessageRouter(deps: {
toolExecutor: deps.toolExecutor,
primaryTier: effectiveTier,
delegation: delegationConfig,
backgroundModelOverrides,
maxDelegationDepth: deps.config.agents.max_delegation_depth ?? 3,
maxIterations: deps.config.agents.max_iterations,
compaction: deps.config.compaction.enabled ? {
@@ -419,6 +471,7 @@ export function createMessageRouter(deps: {
}
const handler = async (msg: InboundMessage, reply: (response: OutboundMessage) => Promise<void>): Promise<void> => {
const sessionIdForRun = `${msg.channel}:${msg.senderId}`;
let incomingText = msg.text;
let matchedReactionName: string | undefined;
const talkMode = deps.config.audio?.talk_mode;
@@ -721,6 +774,14 @@ export function createMessageRouter(deps: {
session.deleteConfig('modelTier');
return '';
},
cancelRun: () => {
const run = activeRuns.get(session.id);
if (!run || !run.isCancellable()) {
return 'No active operation to cancel.';
}
run.cancel();
return 'Cancellation requested. The active operation will stop at the next safe point.';
},
delegateAgent: async (agentName: string, task: string) => {
const target = agentName.trim();
@@ -1293,6 +1354,7 @@ export function createMessageRouter(deps: {
}
let response: string;
activeRuns.set(sessionIdForRun, agent);
try {
response = await agent.process(messageText, attachments);
} catch (error) {
@@ -1322,6 +1384,8 @@ export function createMessageRouter(deps: {
text: 'Sorry, an error occurred while processing your message.',
replyTo: msg.id,
});
} finally {
activeRuns.delete(sessionIdForRun);
}
};