Files
deepseek-harness/packages/core/agent-loop/tests/model.spec.ts
T

102 lines
4.5 KiB
TypeScript

/** Per-Agent model instances share the loop's projection registration and teardown. */
import { Context } from '@deepseek-ai/cordis'
import AgentRegistry, { AgentModel, modelSelectionProjectionDefinition } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import LlmRuntime from '@deepseek-ai/dsh-llm'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRuntime from '@deepseek-ai/dsh-tools'
import { describe, expect, it, onTestFinished, vi } from 'vitest'
async function dependencies() {
const ctx = new Context()
onTestFinished(() => ctx.fiber.dispose())
await ctx.plugin(LlmRuntime)
await ctx.plugin(SessionStore)
await ctx.plugin(SessionProjectionRegistry)
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRuntime)
await ctx.plugin(AgentRegistry)
return ctx
}
const INITIAL = { provider: 'mock', model: 'initial' }
const SELECTED = { provider: 'mock', model: 'selected' }
describe('Agent model ownership', () => {
it('waits for model projection registration before publishing the factory', async () => {
const ctx = await dependencies()
const ready = Promise.withResolvers<undefined>()
const entered = Promise.withResolvers<undefined>()
onTestFinished(() => { ready.resolve(undefined) })
const register = ctx.sessionProjections.register.bind(ctx.sessionProjections)
vi.spyOn(ctx.sessionProjections, 'register').mockImplementation(async (definition) => {
if (definition.key === modelSelectionProjectionDefinition.key) {
entered.resolve(undefined)
await ready.promise
}
return register(definition)
})
const loading = ctx.plugin(AgentLoop, { agents: [] })
await entered.promise
await expect(ctx.agents.create({ sessionId: SessionId('too-early') })).rejects.toThrow('no agent factory registered')
ready.resolve(undefined)
await loading
const handle = await ctx.agents.create({ sessionId: SessionId('ready'), agentOptions: INITIAL })
expect(handle.agent.model.current()).toEqual(INITIAL)
})
it('binds each model instance to its Agent and makes it available during setup', async () => {
const ctx = await dependencies()
await ctx.plugin(AgentLoop, { agents: [] })
const first = await ctx.agents.create({
sessionId: SessionId('first'),
agentOptions: INITIAL,
setup: (_ctx, agent) => {
expect(agent.model).toBeInstanceOf(AgentModel)
agent.model.select(SELECTED)
},
})
const second = await ctx.agents.create({ sessionId: SessionId('second'), agentOptions: INITIAL })
expect(first.agent.model).not.toBe(second.agent.model)
expect(first.agent.model.current()).toEqual(SELECTED)
expect(second.agent.model.current()).toEqual(INITIAL)
expect(second.agent.session.snapshotEvents()).toEqual([])
expect(ctx.sessionProjections.snapshot(first.agent.session).values.modelSelection)
.toEqual({ lastUsed: null, next: SELECTED })
})
it.each(['loop', 'root'] as const)('retains model selection until Agent cleanup finishes on %s disposal', async (owner) => {
const ctx = await dependencies()
const loop = await ctx.plugin(AgentLoop, { agents: [] })
const entered = Promise.withResolvers<undefined>()
const release = Promise.withResolvers<undefined>()
onTestFinished(() => { release.resolve(undefined) })
const handle = await ctx.agents.create({
sessionId: SessionId('cleanup'),
agentOptions: INITIAL,
setup: (agentCtx, agent) => {
agentCtx.effect(() => async () => {
entered.resolve(undefined)
await release.promise
agent.model.select(SELECTED)
expect(agent.model.current()).toEqual(SELECTED)
})
},
})
const disposal = owner === 'loop' ? loop.dispose() : ctx.fiber.dispose()
await entered.promise
expect(handle.agent.model.current()).toEqual(INITIAL)
release.resolve(undefined)
await disposal
expect(handle.agent.session.snapshotEvents().at(-1)).toMatchObject({ type: 'model/selection', data: SELECTED })
if (owner === 'loop') {
expect(ctx.sessionProjections.snapshot(handle.agent.session).values.modelSelection).toBeUndefined()
await ctx.plugin(AgentLoop, { agents: [] })
expect(ctx.sessionProjections.snapshot(handle.agent.session).values.modelSelection)
.toEqual({ lastUsed: null, next: SELECTED })
}
})
})