/** * Agent-scoped LLM target snapshot shared by interactive front doors. * @module @deepseek-ai/dsh-agent/llm-target */ import type { Context } from 'cordis' import type { LlmCallConfig, ReasoningEffortId } from '@deepseek-ai/dsh-llm' /** Complete provider/model route and optional reasoning effort selected for one live agent. */ export interface AgentLlmTarget { /** Registered provider route. */ provider: string /** Provider-owned model id. */ model: string /** Adapter-owned reasoning effort, or provider/default behavior when absent. */ reasoningEffort?: ReasoningEffortId } /** Mutable selection plus the target captured for the current step. */ export interface AgentLlmTargetRef { /** Target selected for the next step that enters prompt assembly. */ current: AgentLlmTarget | undefined /** Target captured when the current step entered prompt assembly. */ assembled: AgentLlmTarget | undefined } /** * Couple one mutable target to agent-scoped prompt assembly and request routing. * Prompt assembly snapshots the selected target before delegating, then applies * its route to prompt variables and its route/effort to request config so a * concurrent switch takes effect on a later step instead of splitting the two * surfaces. An absent selected effort clears any inherited effort so a model * switch can restore that target's provider/default behavior. * * @param agentCtx - The target agent's scoped context. * @param target - Mutable selection owned by the calling front door. * @returns Disposer for both scoped waterfall listeners. */ export function installAgentLlmTarget(agentCtx: Context, target: AgentLlmTargetRef): () => void { const disposeAssembly = agentCtx.on('system-prompt/assemble', async (_assembly, _context, next) => { const selected = target.current const assembled = await next() target.assembled = selected if (selected === undefined) return assembled return { ...assembled, variables: { ...assembled.variables, provider: selected.provider, model: selected.model, }, } }) const disposeRequest = agentCtx.on( 'agent/request', async (_agent, _turn, _step, _signal, next): Promise => { const resolved = await next() const selected = target.assembled if (selected === undefined) return resolved const { reasoningEffort: _inheritedEffort, ...withoutInheritedEffort } = resolved return { ...withoutInheritedEffort, provider: selected.provider, model: selected.model, ...selected.reasoningEffort === undefined ? {} : { reasoningEffort: selected.reasoningEffort }, } }, ) return () => { disposeAssembly() disposeRequest() } }