refactor(web): publish transient model request capacity (round 1)

This commit is contained in:
Hypatia May
2026-07-28 18:35:39 +08:00
parent 3f0ba77bfa
commit 35b9c454e5
54 changed files with 765 additions and 1352 deletions
@@ -4,23 +4,20 @@
* models, and the prompt-assembly boundary for a running selection change.
*/
import { describe, expect, it, vi } from 'vitest'
import { Context, FiberState } from 'cordis'
import type { Fiber } from 'cordis'
import AgentRegistry, { agentEvents, installAgentLlmTarget } from '@deepseek-ai/dsh-agent'
import type { Agent, AgentLlmTargetRef } from '@deepseek-ai/dsh-agent'
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import AgentRegistry, { agentEvents } from '@deepseek-ai/dsh-agent'
import type { Agent } from '@deepseek-ai/dsh-agent'
import LlmService, { LlmAdapter, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
import type {
GenerateOptions, LlmCallConfig, LlmModelInfo, LlmModelReasoningInfo, LlmProviderInfo,
LlmResolvedModelInfo, StreamChunk,
} from '@deepseek-ai/dsh-llm'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import type { Session } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
import type { RpcRequest } from '@deepseek-ai/dsh-host-apiproxy/api/rpc'
import { RpcId } from '@deepseek-ai/dsh-host-apiproxy/api/rpc'
import type { MuxFrame } from '@deepseek-ai/dsh-host-apiproxy/api'
import { createApiProxy } from '../src/api-proxy.ts'
let nextRpc = 1
@@ -64,40 +61,6 @@ class CatalogAdapter extends LlmAdapter {
}
}
class DeferredCatalogAdapter extends CatalogAdapter {
readonly pending: {
result: PromiseWithResolvers<LlmResolvedModelInfo>
signal: AbortSignal | undefined
}[] = []
constructor() {
super('Deferred', [
{ provider: 'deferred', id: 'lifecycle-model', name: 'Lifecycle model' },
])
}
override resolveModel(
_provider: string,
_model: string,
signal?: AbortSignal,
): Promise<LlmResolvedModelInfo> {
const result = Promise.withResolvers<LlmResolvedModelInfo>()
this.pending.push({ result, signal })
return result.promise
}
resolve(index: number, contextWindow: number): void {
const pending = this.pending[index]
if (pending === undefined) throw new Error(`no pending resolution at index ${String(index)}`)
pending.result.resolve({
provider: 'deferred',
id: 'lifecycle-model',
name: 'Lifecycle model',
context: { contextWindow },
})
}
}
const REASONING: LlmModelReasoningInfo = {
efforts: [
{ id: ReasoningEffortId('off'), name: 'Off' },
@@ -107,18 +70,13 @@ const REASONING: LlmModelReasoningInfo = {
defaultEffort: ReasoningEffortId('high'),
}
async function hostContext(
onSessions?: (fiber: Fiber) => void,
onAgents?: (fiber: Fiber) => void,
): Promise<Context> {
async function hostContext(): Promise<Context> {
const ctx = new Context()
const sessionsFiber = await ctx.plugin(SessionStore)
onSessions?.(sessionsFiber)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt, { persona: '' })
await ctx.plugin(LlmService)
await ctx.plugin(UserInteractionService)
const agentsFiber = await ctx.plugin(AgentRegistry)
onAgents?.(agentsFiber)
await ctx.plugin(AgentRegistry)
ctx.llm.registerAdapter(['deepseek'], new CatalogAdapter('DeepSeek', [
{ provider: 'deepseek', id: 'deepseek-chat', name: 'DeepSeek Chat' },
{ provider: 'deepseek', id: 'deepseek-reasoner', name: 'DeepSeek Reasoner', description: 'Reasoning model' },
@@ -164,65 +122,6 @@ function expectValue<T>(response: { result: { ok: true; value: T } | { ok: false
return response.result.value
}
async function nextMetrics(
iterator: AsyncIterator<RpcRequest<MuxFrame>>,
): Promise<Extract<MuxFrame, { type: 'session/metrics' }>['metrics']> {
for (;;) {
const next = await iterator.next()
if (next.done) throw new Error('mux ended before a metrics frame')
if (next.value.payload.type === 'session/metrics') return next.value.payload.metrics
}
}
function attachLifecycleSession(
ctx: Context,
sessionId: SessionId,
withMarker = false,
): { session: Session; detach: () => void } {
const session = ctx.sessions.prepare(sessionId)
session.append('request/header', {
header: { config: { provider: 'deferred', model: 'lifecycle-model' } },
reason: 'initial',
})
if (withMarker) {
session.append('user/message', {
content: [{ type: 'text', text: 'replacement marker' }],
source: { kind: 'plugin', plugin: 'test' },
}, { surfaceOp: 'append' })
}
const detach = ctx.sessions.enter(session)
ctx.sessions.announce(session)
return { session, detach }
}
function attachLifecycleAgent(
ctx: Context,
session: Session,
): () => void {
const agent = {
id: session.id,
session,
status: 'running',
ctx,
} as Agent
const detach = ctx.agents.enter(agent, undefined)
ctx.agents.announce(agent)
return detach
}
function settleCapacityCompletion(): Promise<void> {
return new Promise<void>((resolve) => { setImmediate(resolve) })
}
function installDeferredAdapter(
ctx: Context,
adapter: DeferredCatalogAdapter,
): Fiber & PromiseLike<Fiber> {
return ctx.plugin(Object.assign((inner: Context) => {
inner.llm.registerAdapter(['deferred'], adapter)
}, { inject: ['llm'] }))
}
describe('Web session model selection', () => {
it('groups successful providers, isolates failures, and preserves an unlisted current model', async () => {
const { ctx, sessionId } = await harness({
@@ -230,7 +129,12 @@ describe('Web session model selection', () => {
model: 'private-preview',
reasoningEffort: ReasoningEffortId('max'),
})
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const api = createApiProxy(ctx, {
provider: 'deepseek',
model: 'deepseek-chat',
cwd: '/tmp',
workspaceRoot: '/tmp',
})
const catalog = expectValue(await api.sessions.models(request({ sessionId })))
expect(catalog.current).toEqual({
@@ -271,7 +175,12 @@ describe('Web session model selection', () => {
it('accepts an advisory-unlisted model, rejects an unavailable provider, and switches only after the next assembly', async () => {
const { ctx, agent, sessionId } = await harness()
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const api = createApiProxy(ctx, {
provider: 'deepseek',
model: 'deepseek-chat',
cwd: '/tmp',
workspaceRoot: '/tmp',
})
const seed: LlmCallConfig = { provider: 'seed', model: 'seed', temperature: 0.2 }
const signal = new AbortController().signal
@@ -336,534 +245,4 @@ describe('Web session model selection', () => {
.toEqual({ provider: 'deepseek', model: 'private-preview', reasoningEffort: 'max' })
await ctx.fiber.dispose()
})
it('publishes unknown capacity immediately on selection, then the exact selected route capacity', async () => {
const { ctx, sessionId } = await harness()
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
expectValue(await api.sessions.models(request({ sessionId })))
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
expect((await nextMetrics(iterator)).contextWindow).toBe(64_000)
expectValue(await api.sessions.selectModel(request({
sessionId,
provider: 'deepseek',
model: 'private-preview',
})))
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
expect((await nextMetrics(iterator)).contextWindow).toBe(128_000)
controller.abort()
await iterator.return?.()
await ctx.fiber.dispose()
})
it('uses logged capacity without installing Web routing while scheduling foreign metrics', async () => {
const ctx = await hostContext()
const api = createApiProxy(ctx, {
provider: 'deepseek',
model: 'deepseek-chat',
cwd: '/tmp',
workspaceRoot: '/tmp',
})
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
const initialMetrics = nextMetrics(iterator)
const session = ctx.sessions.create()
expect((await initialMetrics).contextWindow).toBeUndefined()
session.append('request/header', {
header: { config: { provider: 'deepseek', model: 'private-preview' } },
reason: 'change',
})
const foreign = {
id: session.id,
session,
status: 'running',
ctx,
} as Agent
const foreignTarget: AgentLlmTargetRef = {
current: { provider: 'foreign', model: 'foreign-model' },
assembled: undefined,
}
const disposeForeignTarget = installAgentLlmTarget(foreign.ctx, foreignTarget)
const scheduledMetrics = nextMetrics(iterator)
ctx.agents.register(foreign)
expect((await scheduledMetrics).contextWindow).toBeUndefined()
expect((await nextMetrics(iterator)).contextWindow).toBe(128_000)
expect((await ctx.systemPrompt.assemble()).variables)
.toMatchObject({ provider: 'foreign', model: 'foreign-model' })
const seed: LlmCallConfig = { provider: 'seed', model: 'seed', temperature: 0.2 }
const signal = new AbortController().signal
await expect(agentEvents(ctx, foreign).waterfall(
'agent/request', 1, 0, signal, () => Promise.resolve(seed),
)).resolves.toMatchObject({ provider: 'foreign', model: 'foreign-model' })
disposeForeignTarget()
expect((await ctx.systemPrompt.assemble()).variables).not.toHaveProperty('provider')
await expect(agentEvents(ctx, foreign).waterfall(
'agent/request', 1, 1, signal, () => Promise.resolve(seed),
)).resolves.toBe(seed)
controller.abort()
await iterator.return?.()
await ctx.fiber.dispose()
})
it('drops capacity completion from a replaced agent that retains the exact session', async () => {
const ctx = await hostContext()
const deferred = new DeferredCatalogAdapter()
ctx.llm.registerAdapter(['deferred'], deferred)
const lifecycle = attachLifecycleSession(ctx, SessionId('capacity-agent-lifecycle'))
const retire = attachLifecycleAgent(ctx, lifecycle.session)
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(1) })
retire()
const detachLive = attachLifecycleAgent(ctx, lifecycle.session)
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(2) })
deferred.resolve(0, 64_000)
await settleCapacityCompletion()
deferred.resolve(1, 128_000)
await settleCapacityCompletion()
expect((await nextMetrics(iterator)).contextWindow).toBe(128_000)
controller.abort()
await iterator.return?.()
detachLive()
lifecycle.detach()
await ctx.fiber.dispose()
})
it('drops capacity completion from a replaced session while its old agent remains live', async () => {
const ctx = await hostContext()
const deferred = new DeferredCatalogAdapter()
ctx.llm.registerAdapter(['deferred'], deferred)
const sessionId = SessionId('capacity-session-lifecycle')
const retiredSession = attachLifecycleSession(ctx, sessionId)
const retireAgent = attachLifecycleAgent(ctx, retiredSession.session)
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(1) })
retiredSession.detach()
const liveSession = attachLifecycleSession(ctx, sessionId, true)
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
deferred.resolve(0, 64_000)
await settleCapacityCompletion()
retireAgent()
const detachLiveAgent = attachLifecycleAgent(ctx, liveSession.session)
const scheduled = await nextMetrics(iterator)
expect(scheduled.logRevision).toBe(2)
expect(scheduled.contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(2) })
deferred.resolve(1, 128_000)
await settleCapacityCompletion()
expect((await nextMetrics(iterator)).contextWindow).toBe(128_000)
controller.abort()
await iterator.return?.()
detachLiveAgent()
liveSession.detach()
await ctx.fiber.dispose()
})
it('does not project retired agent capacity into replacement session snapshots', async () => {
const ctx = await hostContext()
const deferred = new DeferredCatalogAdapter()
ctx.llm.registerAdapter(['deferred'], deferred)
const sessionId = SessionId('capacity-snapshot-lifecycle')
const retiredSession = attachLifecycleSession(ctx, sessionId)
const retireAgent = attachLifecycleAgent(ctx, retiredSession.session)
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const primaryController = new AbortController()
const primary = api.events.mux(request({}), primaryController.signal)[Symbol.asyncIterator]()
expect((await nextMetrics(primary)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(1) })
deferred.resolve(0, 64_000)
await settleCapacityCompletion()
expect((await nextMetrics(primary)).contextWindow).toBe(64_000)
retiredSession.detach()
const replacement = attachLifecycleSession(ctx, sessionId)
const createdBaseline = await nextMetrics(primary)
replacement.session.append('user/message', {
content: [{ type: 'text', text: 'replacement marker' }],
source: { kind: 'plugin', plugin: 'test' },
}, { surfaceOp: 'append' })
const scheduledFlush = await nextMetrics(primary)
const reconnectController = new AbortController()
const reconnect = api.events.mux(request({}), reconnectController.signal)[Symbol.asyncIterator]()
const reconnectBaseline = await nextMetrics(reconnect)
expect(createdBaseline.logRevision).toBe(1)
for (const metrics of [scheduledFlush, reconnectBaseline]) {
expect(metrics.logRevision).toBe(2)
}
expect({
created: createdBaseline.contextWindow,
scheduled: scheduledFlush.contextWindow,
reconnect: reconnectBaseline.contextWindow,
}).toEqual({ created: undefined, scheduled: undefined, reconnect: undefined })
retireAgent()
const detachReplacementAgent = attachLifecycleAgent(ctx, replacement.session)
expect((await nextMetrics(primary)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(2) })
deferred.resolve(1, 128_000)
await settleCapacityCompletion()
expect((await nextMetrics(primary)).contextWindow).toBe(128_000)
primaryController.abort()
reconnectController.abort()
await primary.return?.()
await reconnect.return?.()
detachReplacementAgent()
replacement.detach()
await ctx.fiber.dispose()
})
it('refreshes same-route capacity after adapter owner replacement', async () => {
const ctx = await hostContext()
const retiredAdapter = new DeferredCatalogAdapter()
const retiredFiber = await installDeferredAdapter(ctx, retiredAdapter)
const lifecycle = attachLifecycleSession(ctx, SessionId('capacity-adapter-lifecycle'))
const detachAgent = attachLifecycleAgent(ctx, lifecycle.session)
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(retiredAdapter.pending).toHaveLength(1) })
retiredAdapter.resolve(0, 64_000)
await settleCapacityCompletion()
expect((await nextMetrics(iterator)).contextWindow).toBe(64_000)
await retiredFiber.dispose()
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
const replacementAdapter = new DeferredCatalogAdapter()
const replacementFiber = await installDeferredAdapter(ctx, replacementAdapter)
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(replacementAdapter.pending).toHaveLength(1) })
replacementAdapter.resolve(0, 128_000)
await settleCapacityCompletion()
expect((await nextMetrics(iterator)).contextWindow).toBe(128_000)
expect(lifecycle.session.requestHeader()?.config).toMatchObject({
provider: 'deferred',
model: 'lifecycle-model',
})
controller.abort()
await iterator.return?.()
detachAgent()
lifecycle.detach()
await replacementFiber.dispose()
await ctx.fiber.dispose()
})
it('aborts pending capacity during adapter UNLOADING and refreshes after settlement', async () => {
const ctx = await hostContext()
const retiredAdapter = new DeferredCatalogAdapter()
const releaseUnload = Promise.withResolvers<undefined>()
const releaseCancellationWait = Promise.withResolvers<undefined>()
const abortObserved = Promise.withResolvers<undefined>()
const retiredFiber = await ctx.plugin(Object.assign((inner: Context) => {
inner.llm.registerAdapter(['deferred'], retiredAdapter)
inner.effect(
() => () => releaseUnload.promise,
'test: hold adapter unload',
)
inner.effect(() => () => {
const signal = retiredAdapter.pending[0]?.signal
if (signal === undefined) throw new Error('pending capacity signal missing')
const cancellation = new Promise<void>((resolve) => {
const finish = () => {
abortObserved.resolve(undefined)
resolve()
}
if (signal.aborted) finish()
else signal.addEventListener('abort', finish, { once: true })
})
return Promise.race([cancellation, releaseCancellationWait.promise])
}, 'test: await capacity cancellation')
}, { inject: ['llm'] }))
const lifecycle = attachLifecycleSession(ctx, SessionId('capacity-adapter-unloading'))
const detachAgent = attachLifecycleAgent(ctx, lifecycle.session)
const api = createApiProxy(ctx, {
provider: 'deepseek',
model: 'deepseek-chat',
cwd: '/tmp',
workspaceRoot: '/tmp',
})
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
const resolveModelInfo = vi.spyOn(ctx.llm, 'resolveModelInfo')
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(retiredAdapter.pending).toHaveLength(1) })
expect(resolveModelInfo).toHaveBeenCalledOnce()
const listSessions = vi.spyOn(ctx.sessions, 'list')
listSessions.mockClear()
const pendingFrame = iterator.next()
const disposing = retiredFiber.dispose()
try {
await vi.waitFor(() => {
expect(retiredAdapter.pending[0]?.signal?.aborted).toBe(true)
})
await abortObserved.promise
expect(retiredFiber.state).toBe(FiberState.UNLOADING)
retiredAdapter.resolve(0, 64_000)
await settleCapacityCompletion()
expect(resolveModelInfo).toHaveBeenCalledOnce()
expect(listSessions).not.toHaveBeenCalled()
const outcome = await Promise.race([
pendingFrame.then(() => 'frame' as const),
new Promise<'idle'>((resolve) => { setImmediate(() => { resolve('idle') }) }),
])
expect(outcome).toBe('idle')
} finally {
releaseCancellationWait.resolve(undefined)
releaseUnload.resolve(undefined)
await disposing
}
expect(retiredFiber.state).toBe(FiberState.DISPOSED)
const settledFrame = await pendingFrame
if (settledFrame.done || settledFrame.value.payload.type !== 'session/metrics') {
throw new Error('expected settled metrics refresh')
}
expect(settledFrame.value.payload.metrics.contextWindow).toBeUndefined()
await settleCapacityCompletion()
expect(resolveModelInfo).toHaveBeenCalledTimes(2)
const replacementAdapter = new DeferredCatalogAdapter()
const replacementFiber = await installDeferredAdapter(ctx, replacementAdapter)
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(replacementAdapter.pending).toHaveLength(1) })
expect(resolveModelInfo).toHaveBeenCalledTimes(3)
replacementAdapter.resolve(0, 128_000)
await settleCapacityCompletion()
expect((await nextMetrics(iterator)).contextWindow).toBe(128_000)
controller.abort()
await iterator.return?.()
detachAgent()
lifecycle.detach()
await replacementFiber.dispose()
await ctx.fiber.dispose()
})
it('aborts pending capacity when the API proxy fiber is disposed', async () => {
const ctx = await hostContext()
const deferred = new DeferredCatalogAdapter()
ctx.llm.registerAdapter(['deferred'], deferred)
const lifecycle = attachLifecycleSession(ctx, SessionId('capacity-api-proxy-teardown'))
const detachAgent = attachLifecycleAgent(ctx, lifecycle.session)
const proxy = Promise.withResolvers<ReturnType<typeof createApiProxy>>()
const proxyFiber = await ctx.plugin(Object.assign((inner: Context) => {
proxy.resolve(createApiProxy(inner, {
provider: 'deepseek',
model: 'deepseek-chat',
cwd: '/tmp',
workspaceRoot: '/tmp',
}))
}, { inject: ['agents', 'sessions', 'userInteraction'] }))
const api = await proxy.promise
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(1) })
expect(deferred.pending[0]?.signal?.aborted).toBe(false)
await proxyFiber.dispose()
expect(deferred.pending[0]?.signal?.aborted).toBe(true)
deferred.resolve(0, 64_000)
await settleCapacityCompletion()
const pendingFrame = iterator.next()
const outcome = await Promise.race([
pendingFrame.then(() => 'frame' as const),
new Promise<'idle'>((resolve) => { setImmediate(() => { resolve('idle') }) }),
])
expect(outcome).toBe('idle')
controller.abort()
await expect(pendingFrame).resolves.toMatchObject({ done: true })
await iterator.return?.()
detachAgent()
lifecycle.detach()
await ctx.fiber.dispose()
})
it('does not read the sessions service after its disposal status', async () => {
let sessionsFiber: Fiber | undefined
const ctx = await hostContext((fiber) => { sessionsFiber = fiber })
if (sessionsFiber === undefined) throw new Error('sessions fiber missing')
createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const sessions = ctx.get('sessions')
if (sessions === undefined) throw new Error('sessions service missing')
const list = vi.spyOn(sessions, 'list').mockImplementation(() => {
throw new Error('disposed sessions service read')
})
await expect(sessionsFiber.dispose()).resolves.toBeUndefined()
expect(list).not.toHaveBeenCalled()
await ctx.fiber.dispose()
})
it('invalidates pending capacity before SessionStore teardown can reach its callback', async () => {
let sessionsFiber: Fiber | undefined
const ctx = await hostContext((fiber) => { sessionsFiber = fiber })
if (sessionsFiber === undefined) throw new Error('sessions fiber missing')
const deferred = new DeferredCatalogAdapter()
ctx.llm.registerAdapter(['deferred'], deferred)
const lifecycle = attachLifecycleSession(ctx, SessionId('capacity-session-store-teardown'))
const detachAgent = attachLifecycleAgent(ctx, lifecycle.session)
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(1) })
const agents = ctx.get('agents')
if (agents === undefined) throw new Error('agent registry missing')
expect(agents.get(lifecycle.session.id)).toBeDefined()
const getAgent = vi.spyOn(agents, 'get')
await sessionsFiber.dispose()
getAgent.mockClear()
deferred.resolve(0, 64_000)
await settleCapacityCompletion()
expect(getAgent).not.toHaveBeenCalled()
const pendingFrame = iterator.next()
const outcome = await Promise.race([
pendingFrame.then(() => 'frame' as const),
new Promise<'idle'>((resolve) => { setImmediate(() => { resolve('idle') }) }),
])
expect(outcome).toBe('idle')
controller.abort()
await expect(pendingFrame).resolves.toMatchObject({ done: true })
await iterator.return?.()
detachAgent()
lifecycle.detach()
await ctx.fiber.dispose()
})
it('publishes unknown metrics after AgentRegistry terminal disposal with mux active', async () => {
let agentsFiber: Fiber | undefined
const ctx = await hostContext(undefined, (fiber) => { agentsFiber = fiber })
if (agentsFiber === undefined) throw new Error('agent registry fiber missing')
const deferred = new DeferredCatalogAdapter()
ctx.llm.registerAdapter(['deferred'], deferred)
const lifecycle = attachLifecycleSession(ctx, SessionId('capacity-agent-registry-disposed'))
const detachAgent = attachLifecycleAgent(ctx, lifecycle.session)
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(1) })
deferred.resolve(0, 64_000)
await settleCapacityCompletion()
expect((await nextMetrics(iterator)).contextWindow).toBe(64_000)
await agentsFiber.dispose()
const refresh = nextMetrics(iterator).then(
metrics => ({ kind: 'metrics' as const, metrics }),
() => ({ kind: 'error' as const }),
)
const outcome = await Promise.race([
refresh,
new Promise<{ kind: 'idle' }>((resolve) => {
setImmediate(() => { resolve({ kind: 'idle' }) })
}),
])
controller.abort()
await refresh
await iterator.return?.()
detachAgent()
lifecycle.detach()
await ctx.fiber.dispose()
expect(outcome.kind).toBe('metrics')
if (outcome.kind === 'metrics') {
expect(outcome.metrics.contextWindow).toBeUndefined()
expect(outcome.metrics.logRevision).toBe(1)
}
})
it.each(['agents', 'sessions'] as const)(
'drops capacity completion while %s is unavailable during unload',
async (serviceName) => {
let sessionsFiber: Fiber | undefined
let agentsFiber: Fiber | undefined
const ctx = await hostContext(
(fiber) => { sessionsFiber = fiber },
(fiber) => { agentsFiber = fiber },
)
const heldFiber = serviceName === 'sessions' ? sessionsFiber : agentsFiber
if (heldFiber === undefined) throw new Error(`${serviceName} fiber missing`)
const unloadStarted = Promise.withResolvers<undefined>()
const releaseUnload = Promise.withResolvers<undefined>()
heldFiber.ctx.effect(() => () => {
unloadStarted.resolve(undefined)
return releaseUnload.promise
}, `test: hold ${serviceName} unload`)
const deferred = new DeferredCatalogAdapter()
ctx.llm.registerAdapter(['deferred'], deferred)
const lifecycle = attachLifecycleSession(ctx, SessionId(`capacity-${serviceName}-unloading`))
const detachAgent = attachLifecycleAgent(ctx, lifecycle.session)
const api = createApiProxy(ctx, { provider: 'deepseek', model: 'deepseek-chat', cwd: '/tmp', workspaceRoot: '/tmp' })
const controller = new AbortController()
const iterator = api.events.mux(request({}), controller.signal)[Symbol.asyncIterator]()
expect((await nextMetrics(iterator)).contextWindow).toBeUndefined()
await vi.waitFor(() => { expect(deferred.pending).toHaveLength(1) })
const agents = ctx.get('agents')
if (agents === undefined) throw new Error('agent registry missing')
const sessions = ctx.get('sessions')
if (sessions === undefined) throw new Error('sessions service missing')
const getAgent = vi.spyOn(agents, 'get')
const getSession = vi.spyOn(sessions, 'get')
const disposing = heldFiber.dispose()
await unloadStarted.promise
await vi.waitFor(() => { expect(ctx.get(serviceName)).toBeUndefined() })
expect(heldFiber.state).toBe(FiberState.UNLOADING)
getAgent.mockClear()
getSession.mockClear()
deferred.resolve(0, 64_000)
await settleCapacityCompletion()
const agentReads = getAgent.mock.calls.length
const sessionReads = getSession.mock.calls.length
const pendingFrame = iterator.next()
const outcome = await Promise.race([
pendingFrame.then(() => 'frame' as const),
new Promise<'idle'>((resolve) => { setImmediate(() => { resolve('idle') }) }),
])
controller.abort()
await expect(pendingFrame).resolves.toMatchObject({ done: true })
await iterator.return?.()
releaseUnload.resolve(undefined)
await disposing
detachAgent()
lifecycle.detach()
await ctx.fiber.dispose()
expect(agentReads).toBe(0)
expect(sessionReads).toBe(0)
expect(outcome).toBe('idle')
},
)
})