| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- /** Default-off settings and per-session model-selection decisions. */
- import { describe, expect, it, vi } from 'vitest'
- import { Context } from '@deepseek-ai/cordis'
- import { ToolCallId } from '@deepseek-ai/dsh-llm'
- import { Session, SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/dsh-session'
- import type { SessionEvent } from '@deepseek-ai/dsh-session'
- import { bindScopeParent, createScope, scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope'
- import { SettingsProvider } from '@deepseek-ai/dsh-settings'
- import type { SettingsNamespace } from '@deepseek-ai/dsh-settings'
- import InvariantRegistry from '@deepseek-ai/dsh-invariants'
- import AgentLoop from '@deepseek-ai/dsh-agent-loop'
- import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
- import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
- import SubagentRuntime from '@deepseek-ai/dsh-subagent'
- import * as SubagentSpawn from '@deepseek-ai/dsh-subagent-spawn-in-process'
- import * as tool from '../src/index.ts'
- import * as ToolInvariant from '../src/invariant.ts'
- import SubagentModelSelectionConfig, {
- SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE,
- } from '../src/model-selection-settings.ts'
- import {
- subagentModelSelectionPolicy,
- subagentModelSelectionProjectionDefinition,
- } from '../src/model-selection-state.ts'
- import { text } from './harness.ts'
- const ALLOWED_MODELS = [{ provider: 'alpha', model: 'fast-model' }]
- /** Writable in-memory settings provider for the package integration. */
- class MemorySettings extends SettingsProvider {
- doc: Record<string, unknown> = {}
- get writable(): boolean {
- return true
- }
- protected load(): Promise<Record<string, unknown>> {
- return Promise.resolve(structuredClone(this.doc))
- }
- protected persist(ns: SettingsNamespace, section: Record<string, unknown>): Promise<void> {
- this.doc = { ...this.doc, [ns]: structuredClone(section) }
- return Promise.resolve()
- }
- }
- /** Read whether one Agent's delegation definition contains route fields. */
- function selectable(ctx: Context, agent: Awaited<ReturnType<Context['agents']['create']>>['agent']): boolean {
- const schema = ctx.tools.schemas(agent).find(candidate => candidate.name === 'subagent')
- const properties = (schema?.parameters as { properties?: Record<string, unknown> } | undefined)?.properties
- return properties?.['provider'] !== undefined
- && properties['model'] !== undefined
- && properties['reasoning_effort'] !== undefined
- && ctx.tools.schemas(agent).some(candidate => candidate.name === 'list_subagent_models')
- }
- const modelSelectionPresets = new WeakMap<Context, ReturnType<typeof createScope>>()
- /** Mount the real settings, Agent, provider, and optional preset tool services. */
- async function boot(withPreset = true): Promise<Context> {
- const ctx = new Context()
- await ctx.plugin(MemorySettings)
- await ctx.plugin(SubagentModelSelectionConfig)
- await mountAgentLoopTestDependencies(ctx)
- await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(SubagentRuntime)
- await ctx.plugin(SubagentSpawn, { providerName: 'spawn' })
- if (withPreset) {
- const preset = createScope(ctx, { preset: 'model-selection-test' })
- await preset.ctx.plugin(tool, {
- provider: 'spawn',
- modelSelectionSettings: true,
- backgroundMode: 'continuable',
- })
- modelSelectionPresets.set(ctx, preset)
- }
- return ctx
- }
- /** Create one Agent joined to the test's standing preset. */
- async function createAgent(ctx: Context, id: string, options: {
- meta?: { parentSession: SessionId; origin: 'subagent' }
- seed?: readonly SessionEvent[]
- } = {}) {
- const preset = modelSelectionPresets.get(ctx)
- if (preset === undefined) throw new Error('context has no model-selection preset')
- const handle = await ctx.agents.create({
- sessionId: SessionId(id),
- ...options,
- setup: (agentCtx) => {
- bindScopeParent(scopeOf(agentCtx)!, scopeOf(preset.ctx)!)
- },
- })
- return handle.agent
- }
- describe('SubagentModelSelectionConfig', () => {
- it('uses the composed default without a settings provider', async () => {
- const ctx = new Context()
- await ctx.plugin(SubagentModelSelectionConfig, { enabled: true, allowedModels: ALLOWED_MODELS })
- expect(ctx.subagentModelSelection.current()).toEqual({ enabled: true, allowedModels: ALLOWED_MODELS })
- await ctx.fiber.dispose()
- })
- it('defaults off and follows the validated user layer', async () => {
- const ctx = new Context()
- await ctx.plugin(MemorySettings)
- await ctx.plugin(SubagentModelSelectionConfig)
- expect(ctx.subagentModelSelection.current()).toEqual({ enabled: false, allowedModels: [] })
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: true,
- allowedModels: ALLOWED_MODELS,
- })
- expect(ctx.subagentModelSelection.current()).toEqual({ enabled: true, allowedModels: ALLOWED_MODELS })
- await ctx.fiber.dispose()
- })
- it('rejects duplicate routes, enabled empty settings, and an empty durable policy', async () => {
- const ctx = new Context()
- await ctx.plugin(MemorySettings)
- await ctx.plugin(SubagentModelSelectionConfig)
- await ctx.plugin(SessionProjectionRegistry)
- ctx.sessionProjections.register(subagentModelSelectionProjectionDefinition)
- await expect(ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- allowedModels: [...ALLOWED_MODELS, ...ALLOWED_MODELS],
- })).rejects.toThrow('repeats route "alpha/fast-model"')
- await expect(ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: true,
- allowedModels: [],
- })).rejects.toThrow('enabled subagent model selection requires at least one allowed model')
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: false,
- allowedModels: ALLOWED_MODELS,
- })
- expect(ctx.subagentModelSelection.current()).toEqual({ enabled: false, allowedModels: ALLOWED_MODELS })
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: [] })
- expect(ctx.subagentModelSelection.current()).toEqual({ enabled: false, allowedModels: [] })
- const invalid = Session.create(SessionId('empty-policy'))
- invalid.append('subagent/model-selection-policy', { allowedModels: [] })
- expect(() => subagentModelSelectionPolicy(ctx.sessionProjections, invalid)).toThrow('requires at least one route')
- const malformed = Session.create(SessionId('malformed-policy'))
- malformed.append('subagent/model-selection-policy', {
- allowedModels: [{ provider: 1, model: 'fast-model' }],
- } as never)
- expect(() => subagentModelSelectionPolicy(ctx.sessionProjections, malformed))
- .toThrow('requires non-empty provider and model ids')
- await ctx.fiber.dispose()
- })
- it('samples each new root Session without changing existing definitions', async () => {
- const ctx = await boot()
- const disabled = await createAgent(ctx, 'disabled')
- expect(selectable(ctx, disabled)).toBe(false)
- expect(subagentModelSelectionPolicy(ctx.sessionProjections, disabled.session)).toBeUndefined()
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: true,
- allowedModels: ALLOWED_MODELS,
- })
- const enabled = await createAgent(ctx, 'enabled')
- expect(subagentModelSelectionPolicy(ctx.sessionProjections, enabled.session)).toEqual(ALLOWED_MODELS)
- expect(selectable(ctx, enabled)).toBe(true)
- expect(selectable(ctx, disabled)).toBe(false)
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { enabled: false })
- const disabledAgain = await createAgent(ctx, 'disabled-again')
- expect(selectable(ctx, disabledAgain)).toBe(false)
- expect(selectable(ctx, enabled)).toBe(true)
- await ctx.fiber.dispose()
- })
- it('installs a direct Agent setup before Session publication', async () => {
- const ctx = await boot(false)
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: true,
- allowedModels: ALLOWED_MODELS,
- })
- let prepared: Awaited<ReturnType<Context['agents']['create']>>['agent'] | undefined
- let visibleAtSessionCreated = false
- ctx.on('session/created', () => {
- visibleAtSessionCreated = prepared !== undefined && selectable(ctx, prepared)
- })
- const handle = await ctx.agents.create({
- sessionId: SessionId('direct-agent-setup'),
- setup: async (agentCtx, agent) => {
- prepared = agent
- const fiber = agentCtx.inject(tool.inject, (runtimeCtx) => {
- tool.apply(runtimeCtx, {
- provider: 'spawn',
- modelSelectionSettings: true,
- backgroundMode: 'continuable',
- }, agent.session)
- })
- await fiber.await()
- },
- })
- expect(visibleAtSessionCreated).toBe(true)
- expect(selectable(ctx, handle.agent)).toBe(true)
- await handle.dispose()
- await ctx.fiber.dispose()
- })
- it('rejects a forced route outside the Session policy before child creation', async () => {
- const ctx = await boot()
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: true,
- allowedModels: ALLOWED_MODELS,
- })
- const agent = await createAgent(ctx, 'enforced')
- const result = await ctx.tools.execute({
- signal: new AbortController().signal,
- callId: ToolCallId('disallowed-session-route'),
- name: 'subagent',
- arguments: {
- description: 'forced route',
- prompt: 'do it',
- provider: 'alpha',
- model: 'other-model',
- },
- agent,
- })
- expect(result.isError).toBe(true)
- expect(text(result)).toContain('is not allowed for this Session')
- await ctx.fiber.dispose()
- })
- it('installs per-Agent definitions for a shared preset scope', async () => {
- const ctx = await boot(false)
- await ctx.plugin(InvariantRegistry, { enabled: true })
- await ctx.plugin(ToolInvariant)
- const preset = createScope(ctx, { preset: 'standard' })
- const other = createScope(ctx, { preset: 'minimal' })
- await preset.ctx.plugin(tool, {
- provider: 'spawn',
- modelSelectionSettings: true,
- backgroundMode: 'continuable',
- })
- let enabledBinding: ReturnType<typeof bindScopeParent> | undefined
- const createComposed = async (id: string) => ctx.agents.create({
- sessionId: SessionId(id),
- setup: (agentCtx) => {
- const binding = bindScopeParent(scopeOf(agentCtx)!, scopeOf(preset.ctx)!)
- if (id === 'preset-enabled') enabledBinding = binding
- },
- })
- const disabled = await createComposed('preset-disabled')
- expect(selectable(ctx, disabled.agent)).toBe(false)
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: true,
- allowedModels: ALLOWED_MODELS,
- })
- const enabled = await createComposed('preset-enabled')
- expect(selectable(ctx, enabled.agent)).toBe(true)
- expect(selectable(ctx, disabled.agent)).toBe(false)
- enabledBinding!.rebind(scopeOf(other.ctx)!)
- ctx.emit(scopeTarget({}, scopeOf(preset.ctx)), 'tools/change')
- await vi.waitFor(() => { expect(selectable(ctx, enabled.agent)).toBe(false) })
- const next = () => Promise.resolve({ kind: 'enter' as const, messages: [] })
- const payload = {
- agent: enabled.agent,
- messages: [],
- turn: 1,
- step: 1,
- signal: new AbortController().signal,
- }
- await expect(ctx.waterfall(ctx as never, 'agent/pre-step', payload, next))
- .resolves.toEqual({ kind: 'enter', messages: [] })
- enabledBinding!.rebind(scopeOf(preset.ctx)!)
- ctx.emit(scopeTarget({}, scopeOf(preset.ctx)), 'tools/change')
- await vi.waitFor(() => { expect(selectable(ctx, enabled.agent)).toBe(true) })
- await expect(ctx.waterfall(ctx as never, 'agent/pre-step', payload, next))
- .resolves.toEqual({ kind: 'enter', messages: [] })
- await enabled.dispose()
- ctx.emit(scopeTarget({}, scopeOf(preset.ctx)), 'tools/change')
- await disabled.dispose()
- await ctx.fiber.dispose()
- })
- it('releases a shared-preset installation reservation after policy selection fails', async () => {
- const ctx = await boot(false)
- const preset = createScope(ctx, { preset: 'standard' })
- const other = createScope(ctx, { preset: 'minimal' })
- await preset.ctx.plugin(tool, {
- provider: 'spawn',
- modelSelectionSettings: true,
- backgroundMode: 'continuable',
- })
- let binding: ReturnType<typeof bindScopeParent> | undefined
- const handle = await ctx.agents.create({
- sessionId: SessionId('preset-policy-retry'),
- setup: (agentCtx) => {
- binding = bindScopeParent(scopeOf(agentCtx)!, scopeOf(preset.ctx)!)
- },
- })
- expect(selectable(ctx, handle.agent)).toBe(false)
- binding!.rebind(scopeOf(other.ctx)!)
- ctx.emit(scopeTarget({}, scopeOf(preset.ctx)), 'tools/change')
- binding!.rebind(scopeOf(preset.ctx)!)
- vi.spyOn(ctx.subagentModelSelection, 'current')
- .mockImplementationOnce(() => { throw new Error('transient settings read') })
- .mockReturnValue({ enabled: true, allowedModels: ALLOWED_MODELS })
- expect(() => { ctx.emit(scopeTarget({}, scopeOf(preset.ctx)), 'tools/change') })
- .toThrow('transient settings read')
- ctx.emit(scopeTarget({}, scopeOf(preset.ctx)), 'tools/change')
- await vi.waitFor(() => { expect(selectable(ctx, handle.agent)).toBe(true) })
- await handle.dispose()
- await ctx.fiber.dispose()
- })
- it('inherits the parent decision and preserves seeded decisions across composition', async () => {
- const ctx = await boot()
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: true,
- allowedModels: ALLOWED_MODELS,
- })
- const parent = await createAgent(ctx, 'parent')
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { enabled: false })
- const child = await createAgent(ctx, 'child', {
- meta: { parentSession: parent.id, origin: 'subagent' },
- })
- expect(selectable(ctx, child)).toBe(true)
- expect(subagentModelSelectionPolicy(ctx.sessionProjections, child.session)).toEqual(ALLOWED_MODELS)
- const orphan = await createAgent(ctx, 'orphan', {
- meta: { parentSession: SessionId('missing-parent'), origin: 'subagent' },
- })
- expect(selectable(ctx, orphan)).toBe(false)
- const enabledSeed = Session.create(SessionId('enabled-seed'))
- enabledSeed.append('subagent/model-selection-policy', { allowedModels: ALLOWED_MODELS })
- const resumedEnabled = await createAgent(ctx, 'resumed-enabled', { seed: enabledSeed.snapshotEvents() })
- expect(selectable(ctx, resumedEnabled)).toBe(true)
- const oldSeed = Session.create(SessionId('old-seed'), [])
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: true,
- allowedModels: ALLOWED_MODELS,
- })
- const resumedEmpty = await createAgent(ctx, 'resumed-empty', { seed: [] })
- expect(selectable(ctx, resumedEmpty)).toBe(false)
- expect(subagentModelSelectionPolicy(ctx.sessionProjections, resumedEmpty.session)).toBeUndefined()
- const resumedDisabled = await createAgent(ctx, 'resumed-disabled', { seed: oldSeed.snapshotEvents() })
- expect(selectable(ctx, resumedDisabled)).toBe(false)
- expect(subagentModelSelectionPolicy(ctx.sessionProjections, resumedDisabled.session)).toBeUndefined()
- await ctx.fiber.dispose()
- })
- it('requires both the Host setting owner and a scoped standing preset', async () => {
- const withoutSettings = new Context()
- await mountAgentLoopTestDependencies(withoutSettings)
- await withoutSettings.plugin(SubagentRuntime)
- expect(() => {
- tool.apply(withoutSettings, {
- provider: 'missing',
- modelSelectionSettings: true,
- maxDepth: 'provider-managed',
- })
- }).toThrow('requires @deepseek-ai/dsh-tool-subagent/model-selection-settings')
- await withoutSettings.fiber.dispose()
- const withoutAgent = await boot(false)
- expect(() => {
- tool.apply(withoutAgent, {
- provider: 'spawn',
- modelSelectionSettings: true,
- backgroundMode: 'continuable',
- })
- }).toThrow('requires a scoped preset Context')
- await withoutAgent.fiber.dispose()
- })
- it('requires the Session registry when a child inherits its parent policy', async () => {
- const ctx = new Context()
- try {
- await ctx.plugin(SubagentModelSelectionConfig)
- await ctx.plugin(SessionProjectionRegistry)
- await ctx.plugin(SubagentRuntime)
- const childId = SessionId('child-without-session-registry')
- const child = Session.create(childId, undefined, {
- version: SESSION_FORMAT_VERSION,
- id: childId,
- createdAt: 1,
- isSeeded: false,
- origin: 'subagent',
- parentSession: SessionId('missing-parent'),
- })
- expect(() => {
- tool.apply(ctx, {
- provider: 'missing',
- modelSelectionSettings: true,
- maxDepth: 'provider-managed',
- }, child)
- }).toThrow('child model-selection inheritance requires the Session registry')
- } finally {
- await ctx.fiber.dispose()
- }
- })
- it('checks model-selectable definitions without rejecting a policy-only preset', async () => {
- const ctx = await boot()
- await ctx.plugin(InvariantRegistry, { enabled: true })
- await ctx.plugin(ToolInvariant)
- const disabled = await createAgent(ctx, 'invariant-disabled')
- const next = () => Promise.resolve({ kind: 'enter' as const, messages: [] })
- const payload = {
- agent: disabled,
- messages: [],
- turn: 1,
- step: 1,
- signal: new AbortController().signal,
- }
- await expect(ctx.waterfall(ctx as never, 'agent/pre-step', payload, next)).resolves.toEqual({
- kind: 'enter', messages: [],
- })
- disabled.session.append('subagent/model-selection-policy', { allowedModels: ALLOWED_MODELS })
- await expect(ctx.waterfall(ctx as never, 'agent/pre-step', payload, next))
- .resolves.toEqual({ kind: 'enter', messages: [] })
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
- enabled: true,
- allowedModels: ALLOWED_MODELS,
- })
- const enabled = await createAgent(ctx, 'invariant-enabled')
- await expect(ctx.waterfall(ctx as never, 'agent/pre-step', { ...payload, agent: enabled }, next))
- .resolves.toEqual({ kind: 'enter', messages: [] })
- const enabledSchemas = ctx.tools.schemas(enabled)
- const schemas = vi.spyOn(ctx.tools, 'schemas')
- schemas.mockReturnValue(enabledSchemas.filter(schema => schema.name !== 'list_subagent_models'))
- await expect(ctx.waterfall(ctx as never, 'agent/pre-step', { ...payload, agent: enabled }, next))
- .rejects.toThrow('require a durable policy, route fields, and list_subagent_models')
- schemas.mockReturnValue(enabledSchemas)
- await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { enabled: false })
- const withoutPolicy = await createAgent(ctx, 'invariant-without-policy')
- await expect(ctx.waterfall(ctx as never, 'agent/pre-step', { ...payload, agent: withoutPolicy }, next))
- .rejects.toThrow('require a durable policy, route fields, and list_subagent_models')
- await ctx.fiber.dispose()
- })
- })
|