Download packages/core/agent/src/model-selection.ts from SaylorTwift/deepseek-harness: direct link, hf CLI and curl.
- Browser
- Download file 4.88 kB
-
https://huggingface.co/SaylorTwift/deepseek-harness/resolve/main/packages/core/agent/src/model-selection.ts
- Command line
-
hf download hf://SaylorTwift/deepseek-harness/packages/core/agent/src/model-selection.ts
-
curl -L -o model-selection.ts https://huggingface.co/SaylorTwift/deepseek-harness/resolve/main/packages/core/agent/src/model-selection.ts
4.88 kB
| /** | |
| * Agent-scoped model selection shared by runtime entry points. | |
| * @module @deepseek-ai/dsh-agent/model-selection | |
| */ | |
| import type { Context } from '@deepseek-ai/cordis' | |
| import { | |
| boundContextSummary, | |
| createUserMessage, | |
| type LlmCallConfig, | |
| type ReasoningEffortId, | |
| } from '@deepseek-ai/dsh-llm' | |
| import type { PreStepDecision } from './runtime-types.ts' | |
| /** Complete provider, model, and optional reasoning effort selected for one live Agent. */ | |
| export interface ModelSelection { | |
| /** Registered provider route. */ | |
| provider: string | |
| /** Provider-owned model id. */ | |
| model: string | |
| /** Adapter-owned reasoning effort, or provider/default behavior when absent. */ | |
| reasoningEffort?: ReasoningEffortId | |
| } | |
| /** Mutable model selection plus the value captured for the current step. */ | |
| export interface ModelSelectionRef { | |
| /** Model selected for the next step that enters prompt assembly. */ | |
| current: ModelSelection | undefined | |
| /** Selection captured when the current step entered prompt assembly. */ | |
| assembled: ModelSelection | undefined | |
| } | |
| function sameRoute(left: ModelSelection, right: ModelSelection): boolean { | |
| return left.provider === right.provider && left.model === right.model | |
| } | |
| function routeLabel(route: ModelSelection, other: ModelSelection): string { | |
| return route.provider === other.provider ? route.model : `${route.provider}/${route.model}` | |
| } | |
| function modelSwitchNotice(previous: ModelSelection, selected: ModelSelection) { | |
| const from = routeLabel(previous, selected) | |
| const to = routeLabel(selected, previous) | |
| return createUserMessage({ | |
| content: [{ | |
| type: 'text' as const, | |
| text: `[model changed: assistant turns above this point were generated by ${from}; the session continues with ${to}]`, | |
| }], | |
| source: { | |
| kind: 'plugin' as const, | |
| plugin: 'model-selection', | |
| form: 'notice' as const, | |
| summary: boundContextSummary(`${from} → ${to}`), | |
| }, | |
| }) | |
| } | |
| /** | |
| * Couple one mutable selection to Agent-scoped prompt assembly and request routing. | |
| * Prompt assembly snapshots the selected model before delegating, then applies | |
| * its provider/model pair and 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, restoring | |
| * the selected model's provider/default behavior. | |
| * | |
| * A provider/model change appends a durable user-role notice to the next | |
| * admitted request. It compares the assembled selection with the latest | |
| * request header; effort-only changes and empty no-request decisions add no | |
| * notice. Failure before header persistence repeats the notice on the next | |
| * request. | |
| * | |
| * @param agentCtx - The selected Agent's scoped context. | |
| * @param selection - Mutable selection owned by the calling entry point. | |
| * @returns Disposer for all scoped waterfall listeners. | |
| */ | |
| export function installModelSelection(agentCtx: Context, selection: ModelSelectionRef): () => void { | |
| const disposeAssembly = agentCtx.on('system-prompt/assemble', async (_assembly, _context, next) => { | |
| const selected = selection.current | |
| const assembled = await next() | |
| selection.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 (_payload, next): Promise<LlmCallConfig> => { | |
| const resolved = await next() | |
| const selected = selection.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 }, | |
| } | |
| }, | |
| ) | |
| const disposeNotice = agentCtx.on( | |
| 'agent/pre-step', | |
| async ({ agent, messages, signal, step }, next): Promise<PreStepDecision> => { | |
| const decision = await next() | |
| if (decision.kind === 'reject' || signal.aborted) return decision | |
| // The loop skips an empty first step and an emptied offered continuation. | |
| if (decision.messages.length === 0 && (step === 1 || messages.length > 0)) return decision | |
| const selected = selection.assembled | |
| const previous = agent.session.requestHeader()?.config | |
| if (selected === undefined || previous === undefined || sameRoute(selected, previous)) return decision | |
| return { ...decision, messages: [...decision.messages, modelSwitchNotice(previous, selected)] } | |
| }, | |
| { prepend: true }, | |
| ) | |
| return () => { | |
| disposeAssembly() | |
| disposeRequest() | |
| disposeNotice() | |
| } | |
| } | |