fix(tui): resolve context capacity for active model
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Context } from 'cordis'
|
||||
import type { Terminal } from '@earendil-works/pi-tui'
|
||||
import AgentRegistry, { type Agent, type AgentOptions, type AgentStatus } from '@deepseek-ai/dsh-agent'
|
||||
import type { ContentBlock, LlmModelInfo, LlmProviderInfo } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock, LlmModelContext, LlmModelInfo, LlmProviderInfo } from '@deepseek-ai/dsh-llm'
|
||||
import CommandService from '@deepseek-ai/dsh-commands'
|
||||
import SessionStore, { SessionId, type Session } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
@@ -31,6 +31,7 @@ export interface TuiHarnessOptions {
|
||||
providers: LlmProviderInfo[]
|
||||
models: LlmModelInfo[]
|
||||
listModels?: (provider: string) => Promise<LlmModelInfo[]>
|
||||
resolveModelContext?: (provider: string, model: string) => Promise<LlmModelContext | undefined>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,9 +76,12 @@ export async function createTuiTestHarness<TerminalType extends Terminal, Exit e
|
||||
return catalog.listModels?.(provider)
|
||||
?? Promise.resolve(catalog.models.filter(model => model.provider === provider).map(model => ({ ...model })))
|
||||
},
|
||||
resolveModelContext(provider: string, model: string) {
|
||||
return catalog.resolveModelContext?.(provider, model)
|
||||
?? Promise.resolve({ contextWindow: options.contextWindow ?? 128_000 })
|
||||
},
|
||||
} as never)
|
||||
ctx.provide('tokenMeter', {
|
||||
contextWindow: options.contextWindow ?? 128_000,
|
||||
measure() {
|
||||
return { totalTokens: options.contextTokens ?? 0 }
|
||||
},
|
||||
|
||||
@@ -115,7 +115,6 @@ async function dispose(setupResult: Awaited<ReturnType<typeof setup>>): Promise<
|
||||
|
||||
function provideTokenMeter(ctx: Context): void {
|
||||
ctx.provide('tokenMeter', {
|
||||
contextWindow: 128_000,
|
||||
measure() {
|
||||
return { totalTokens: 0 }
|
||||
},
|
||||
@@ -537,8 +536,10 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
})
|
||||
|
||||
it('opens a keyboard selector and switches the session model without sending slash text to the agent', async () => {
|
||||
const initialContext = Promise.withResolvers<{ contextWindow: number }>()
|
||||
const result = await setup({
|
||||
agentOptions: { provider: 'alpha', model: 'a1' },
|
||||
contextTokens: 50,
|
||||
catalog: {
|
||||
providers: [{ id: 'alpha', name: 'Alpha' }, { id: 'beta', name: 'Beta' }],
|
||||
models: [
|
||||
@@ -547,6 +548,9 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
{ provider: 'beta', id: 'b1', name: 'Beta One' },
|
||||
{ provider: 'beta', id: 'shared', name: 'Beta Shared' },
|
||||
],
|
||||
resolveModelContext: (provider, model) => provider === 'alpha' && model === 'a1'
|
||||
? initialContext.promise
|
||||
: Promise.resolve({ contextWindow: 200 }),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -574,6 +578,9 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
expect(result.terminal.output).toContain('Model selected: beta/b1')
|
||||
expect(result.agent.sent).toEqual([])
|
||||
expect(result.agent.steered).toEqual([])
|
||||
initialContext.resolve({ contextWindow: 100 })
|
||||
await tick()
|
||||
expect(result.terminal.output).not.toContain('50% context tools:compact b1(reasoning:on)')
|
||||
|
||||
result.terminal.send('/model')
|
||||
result.terminal.send('\r')
|
||||
@@ -584,7 +591,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
result.agent.status = 'idle'
|
||||
result.ctx.emit('agent/status', result.agent, 'idle')
|
||||
await tick()
|
||||
expect(result.terminal.output).toContain('tools:compact b1(reasoning:on)')
|
||||
expect(result.terminal.output).toContain('25% context tools:compact b1(reasoning:on)')
|
||||
|
||||
const assembly = await result.ctx.systemPrompt.assemble(assembleContextFor(result.agent))
|
||||
expect(assembly.variables).toMatchObject({ provider: 'beta', model: 'b1' })
|
||||
@@ -620,6 +627,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
catalog: {
|
||||
providers: [{ id: 'alpha', name: 'Alpha' }],
|
||||
models: [{ provider: 'alpha', id: 'a1', name: 'Alpha One' }],
|
||||
resolveModelContext: () => Promise.resolve(undefined),
|
||||
},
|
||||
})
|
||||
unset.terminal.send('/model')
|
||||
@@ -628,6 +636,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
unset.terminal.send('\r')
|
||||
await tick()
|
||||
expect(unset.terminal.output).toContain('Model selected: alpha/a1')
|
||||
expect(unset.terminal.output).toContain('context unknown tools:compact a1(reasoning:on)')
|
||||
await dispose(unset)
|
||||
|
||||
const empty = await setup({ agentOptions: {}, catalog: { providers: [], models: [] } })
|
||||
@@ -649,12 +658,14 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
providers: [{ id: 'deepseek', name: 'DeepSeek' }],
|
||||
models: [],
|
||||
listModels: () => Promise.reject(new Error('catalog offline')),
|
||||
resolveModelContext: () => Promise.reject(new Error('capacity offline')),
|
||||
},
|
||||
})
|
||||
failed.terminal.send('/model')
|
||||
failed.terminal.send('\r')
|
||||
await tick()
|
||||
expect(failed.terminal.output).toContain('Could not read the model catalog: catalog offline')
|
||||
expect(failed.terminal.output).toContain('Could not resolve model context: capacity offline')
|
||||
await dispose(failed)
|
||||
})
|
||||
|
||||
@@ -690,6 +701,21 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
await tick()
|
||||
expect(rejectedResult.terminal.output).not.toContain('late catalog failure')
|
||||
await rejectedResult.ctx.fiber.dispose()
|
||||
|
||||
const context = Promise.withResolvers<{ contextWindow: number }>()
|
||||
const contextResult = await setup({
|
||||
contextTokens: 99,
|
||||
catalog: {
|
||||
providers: [{ id: 'deepseek', name: 'DeepSeek' }],
|
||||
models: [],
|
||||
resolveModelContext: () => context.promise,
|
||||
},
|
||||
})
|
||||
await contextResult.controller.dispose()
|
||||
context.resolve({ contextWindow: 100 })
|
||||
await tick()
|
||||
expect(contextResult.terminal.output).not.toContain('99% context')
|
||||
await contextResult.ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('discovers and executes plugin commands, then removes TUI-local commands on disposal', async () => {
|
||||
|
||||
Reference in New Issue
Block a user