/** * ui-model browser half on a real cordis Context with fake command/slots/ * connection faces and real session scopes: the plugin mounts ModelService * as `models`, the /model contribution and the conversation.input.model * seat both register, and BOTH entries resolve the SAME per-session * directory through the service — a selection submitted through the seat's * inject face is the current the popup's next options pass marks active * (and the reverse), the one-shared-state contract of the dual entry. * Scope disposal drops the directory (HMR safety). */ import { Context } from 'cordis' import { describe, expect, it } from 'vitest' import { createScope } from '@deepseek-ai/dsh-client-runtime/client' import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client' import { LocaleService } from '@deepseek-ai/dsh-client-locale/client' import type { ModelTarget } from '@deepseek-ai/dsh-client-connection/client' import type { CommandContribution, SelectOption } from '@deepseek-ai/dsh-client-ui-command/client' import type { ModelSelectInjected } from '../src/client/slots.ts' import { apply, inject } from '../src/client/index.ts' const sid = (k: string): SessionId => k as SessionId const GROUPS = [{ id: 'deepseek-official', name: 'DeepSeek', models: [ { id: 'deepseek-v4-flash', name: 'DeepSeek-V4-Flash', reasoning: { efforts: [ { id: 'off', name: 'Off' }, { id: 'high', name: 'High' }, { id: 'max', name: 'Max' }, ], defaultEffort: 'high', }, }, { id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro', reasoning: { efforts: [ { id: 'off', name: 'Off' }, { id: 'high', name: 'High' }, { id: 'max', name: 'Max' }, ], defaultEffort: 'high', }, }, ], }] /** Boot the plugin over fake faces + a stateful fake host (current moves on selectModel). */ async function bench() { const ctx = new Context() let current: ModelTarget = { provider: 'deepseek-official', model: 'deepseek-v4-flash' } const calls = { models: 0, select: 0 } ctx.provide('connection', { api: { sessions: { models: () => { calls.models += 1 return Promise.resolve({ result: { ok: true as const, value: { current, groups: GROUPS, failures: [] } } }) }, selectModel: (payload: { provider: string; model: string; reasoningEffort?: string }) => { calls.select += 1 current = { provider: payload.provider, model: payload.model, ...payload.reasoningEffort === undefined ? {} : { reasoningEffort: payload.reasoningEffort }, } return Promise.resolve({ result: { ok: true as const, value: { selected: current } } }) }, } } }) let contribution: CommandContribution | undefined ctx.provide('command', { register(c: CommandContribution) { contribution = c return () => { contribution = undefined } }, }) const seats = new Map ModelSelectInjected) | undefined locale: string | undefined }>() ctx.provide('slots', { register(options: { name: string; locale?: string; inject?: (sessionId: SessionId) => ModelSelectInjected }) { seats.set(options.name, { inject: options.inject, locale: options.locale }) return () => { seats.delete(options.name) } }, }) ctx.provide('conversation', {}) ctx.provide('locale', new LocaleService(ctx)) const scopes = new Map() const addressed = new Set() ctx.provide('sessions', { scope: (id: SessionId) => scopes.get(id), subagentAddress: (id: SessionId) => addressed.has(id) ? { parentSessionId: sid('parent'), childSessionId: id, mode: 'continuable' as const } : undefined, }) const fiber = ctx.plugin({ inject: [...inject], apply }) await fiber.await() await ctx.plugin(function probe() {}).await() const mint = (key: string) => { const handle = createScope(ctx, sid(key)) scopes.set(sid(key), handle.ctx) return handle } return { ctx, fiber, mint, calls, contribution: () => contribution!, seat: () => seats.get('conversation.input.model')!, hostCurrent: () => current, setHostCurrent: (target: ModelTarget) => { current = target }, address: (id: SessionId) => { addressed.add(id) }, } } const projection = (id: string) => ({ sessionId: sid(id) }) describe('ui-model dual entry', () => { it('registers the /model contribution and the composer model seat', async () => { const b = await bench() expect(b.contribution().name).toBe('model') expect(b.contribution().ui.kind).toBe('popupSelect') expect(b.seat().inject).toBeTypeOf('function') // Copy rides the standard locale seat. expect(b.seat().locale).toBe('model') }) it('popup options mark the host current active with the provider group in the detail', async () => { const b = await bench() b.mint('s1') const options = await b.contribution().ui.options(projection('s1'), new AbortController().signal) expect(options.map((o: SelectOption) => o.label)).toEqual(['DeepSeek-V4-Flash', 'DeepSeek-V4-Pro']) expect(options[0]).toMatchObject({ active: true, detail: 'DeepSeek' }) expect(options[1]?.active).toBeUndefined() }) it('a seat selection is the current the popup marks active next — one shared state', async () => { const b = await bench() b.mint('s1') const seatFace = b.seat().inject!(sid('s1')) // Switch through the SEAT entry. expect(await seatFace.select({ provider: 'deepseek-official', model: 'deepseek-v4-pro', reasoningEffort: 'max', })).toBe(true) expect(b.hostCurrent()).toEqual({ provider: 'deepseek-official', model: 'deepseek-v4-pro', reasoningEffort: 'max', }) expect(seatFace.directory.getSnapshot().current).toEqual({ provider: 'deepseek-official', model: 'deepseek-v4-pro', reasoningEffort: 'max', }) // The POPUP's next options pass reflects it without a seat-side reload. const options = await b.contribution().ui.options(projection('s1'), new AbortController().signal) expect(options.find((o: SelectOption) => o.label === 'DeepSeek-V4-Pro')).toMatchObject({ active: true }) }) it('a popup selection lands on the seat store — the reverse direction of the same state', async () => { const b = await bench() b.mint('s1') const seatFace = b.seat().inject!(sid('s1')) const options = await b.contribution().ui.options(projection('s1'), new AbortController().signal) const pro = options.find((o: SelectOption) => o.label === 'DeepSeek-V4-Pro')! await b.contribution().ui.onSelect(pro, projection('s1')) expect(seatFace.directory.getSnapshot().current).toEqual({ provider: 'deepseek-official', model: 'deepseek-v4-pro', reasoningEffort: 'high', }) }) it('both entries share one directory instance per session, isolated across sessions', async () => { const b = await bench() b.mint('a') b.mint('b') const faceA = b.seat().inject!(sid('a')) const faceA2 = b.seat().inject!(sid('a')) const faceB = b.seat().inject!(sid('b')) expect(faceA.directory).toBe(faceA2.directory) expect(faceA.directory).not.toBe(faceB.directory) // The service face resolves the same instance the seat inject handed out. expect(b.ctx.models.directoryFor(sid('a')).store).toBe(faceA.directory) }) it('drops an unconsumed local selection and restores the Host target after reconnect', async () => { const b = await bench() b.mint('s1') const face = b.seat().inject!(sid('s1')) await face.select({ provider: 'deepseek-official', model: 'deepseek-v4-pro' }) b.setHostCurrent({ provider: 'deepseek-official', model: 'deepseek-v4-flash' }) b.ctx.emit('connection/reset') expect(face.directory.getSnapshot()).toMatchObject({ current: null, status: 'loading' }) await Promise.resolve() expect(face.directory.getSnapshot()).toMatchObject({ current: { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, status: 'ready', }) }) it('scope disposal drops the directory; a reborn scope gets a fresh one', async () => { const b = await bench() const first = b.mint('s1') const face1 = b.seat().inject!(sid('s1')) await first.fiber.dispose() b.mint('s1') const face2 = b.seat().inject!(sid('s1')) expect(face2.directory).not.toBe(face1.directory) }) it('an unknown session fails loud at the seat inject', async () => { const b = await bench() expect(() => b.seat().inject!(sid('ghost'))).toThrow(/resolved no scope/) }) it('withholds both model entries from addressed subagent sessions without Agent-bound RPCs', async () => { const b = await bench() b.mint('child') b.address(sid('child')) expect(b.contribution().available(projection('child'))).toBe(false) await expect(b.contribution().ui.options( projection('child'), new AbortController().signal, )).rejects.toThrow(/unavailable for addressed subagent/) const face = b.seat().inject!(sid('child')) expect(face.available).toBe(false) face.load() await expect(face.select({ provider: 'deepseek', model: 'deepseek-v4-pro' })).resolves.toBe(false) await expect(b.ctx.models.directoryFor(sid('child')).load()) .rejects.toThrow(/unavailable for addressed subagent/) await expect(b.ctx.models.directoryFor(sid('child')).select({ provider: 'deepseek', model: 'deepseek-v4-pro', })).rejects.toThrow(/unavailable for addressed subagent/) b.ctx.emit('connection/reset') await Promise.resolve() expect(b.calls).toEqual({ models: 0, select: 0 }) }) })