refactor(agent): own model selection on each agent
This commit is contained in:
1 parent
639602e63a
commit
a9c006f9c3
237 files changed
+1051
-1734
No files matched your search
@@ -21,8 +21,8 @@
|
||||
en: 41bdda1ac32df558
|
||||
zh: aa5d0e325924066b
|
||||
/session-controller/session-media-references:
|
||||
en: 2f5524fbfc9446e4
|
||||
zh: f72c0bc9607007ae
|
||||
en: 9449c5c3a53031c6
|
||||
zh: 68d8ad0d3632983a
|
||||
/session-controller/configuration:
|
||||
en: 5ae2b340c630897f
|
||||
zh: 1ffbd61ecf302ec2
|
||||
|
||||
@@ -90,7 +90,7 @@ References keep local Session data, scoped Contexts, and history streams alive,
|
||||
|
||||
GUI model selection requires the exact provider/model pair in the available catalog; unavailable choices reject with `session/model-unavailable`. Prompt admission retains the saved route without a catalog availability gate, so request execution reports missing credentials or unavailable models. `initializeDefaultModel()` saves the first available account model after login when no other provider has a configured API key; credential checks use configured references independently of model availability. A provider with no available models rejects initialization with `session/provider-models-unavailable`. Availability never substitutes another model or rewrites a Session selection.
|
||||
|
||||
A successful `selectModel` response acknowledges the recorded Session selection without waiting for the default profile setting to save. The Agent registry owns its projection; a change applies to the next step while a prepared step retains its captured choice. Default saves run in the background in submission order; a failure logs a warning and leaves the Session selection intact. A Session with no recorded selection or request reads the last successfully saved default through live `AgentOptions` accessors. An explicit choice is recorded even when it equals that default.
|
||||
A successful `selectModel` response acknowledges the recorded Session selection without waiting for the default profile setting to save. The loop registers its projection; a change applies to the next step while a prepared step retains its captured choice. Default saves run in the background in submission order; a failure logs a warning and leaves the Session selection intact. A Session with no recorded selection or request reads the last successfully saved default through live `AgentOptions` accessors. An explicit choice is recorded even when it equals that default.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ Client 的首次 `follow`、重连首屏与 `loadOlder()` 至少请求 50 条以
|
||||
|
||||
GUI 模型选择要求确切提供方/模型对出现在可用目录中;不可用的选择以 `session/model-unavailable` 拒绝。提示词准入保留已保存的路由,不按目录可用性阻断发送,由请求执行报告凭据缺失或模型不可用。`initializeDefaultModel()` 在账号登录后、其他提供方均未配置 API key 时,将第一个可用账号模型保存为默认模型;凭据检查使用已配置的引用,不依赖模型是否可用。提供方没有可用模型时,初始化以 `session/provider-models-unavailable` 拒绝。可用性变化不会替换模型或改写会话选择。
|
||||
|
||||
`selectModel` 成功返回表示会话模型选择已记录,不等待默认 profile 设置保存。Agent 注册表拥有该投影;变更从下一个步骤开始生效,已准备的步骤保留捕获的选择。默认设置在后台按提交顺序保存;保存失败会记录警告,并保留会话选择。尚未记录选择或请求的会话通过 `AgentOptions` 的动态访问器读取最近一次成功保存的默认值。显式选择即使与默认值相同也会记录。
|
||||
`selectModel` 成功返回表示会话模型选择已记录,不等待默认 profile 设置保存。循环注册该投影;变更从下一个步骤开始生效,已准备的步骤保留捕获的选择。默认设置在后台按提交顺序保存;保存失败会记录警告,并保留会话选择。尚未记录选择或请求的会话通过 `AgentOptions` 的动态访问器读取最近一次成功保存的默认值。显式选择即使与默认值相同也会记录。
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -108,8 +108,7 @@
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-workspace-path": "workspace:*",
|
||||
"@deepseek-ai/dsh-workspace": "workspace:*",
|
||||
"@deepseek-ai/dsh-working-directory": "workspace:*",
|
||||
"@deepseek-ai/dsh-agent-model": "workspace:*"
|
||||
"@deepseek-ai/dsh-working-directory": "workspace:*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@deepseek-ai/dsh-session-persistence": {
|
||||
@@ -167,7 +166,6 @@
|
||||
"@deepseek-ai/dsh-util-workspace-path": "workspace:*",
|
||||
"@deepseek-ai/dsh-workspace": "workspace:*",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
"@deepseek-ai/dsh-working-directory": "workspace:*",
|
||||
"@deepseek-ai/dsh-agent-model": "workspace:*"
|
||||
"@deepseek-ai/dsh-working-directory": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { mkdir } from 'node:fs/promises'
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { Agent, AgentOptions, AgentSetup } from '@deepseek-ai/dsh-agent'
|
||||
import type {} from '@deepseek-ai/dsh-agent-default-model'
|
||||
import * as ModelSwitchNotice from '@deepseek-ai/dsh-agent-model/notice'
|
||||
import { installModelSelection } from '@deepseek-ai/dsh-agent'
|
||||
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
|
||||
import { brandString } from '@deepseek-ai/dsh-brand'
|
||||
import type { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
@@ -306,13 +306,13 @@ export class ApiSessionAgentController {
|
||||
async composeAgent(presetId: string | undefined): Promise<ApiSessionComposition> {
|
||||
const presets = this.ctx.get('agentPresets')
|
||||
if (presets === undefined) {
|
||||
return { setup: async (agentCtx) => { await agentCtx.plugin(ModelSwitchNotice) } }
|
||||
return { setup: (agentCtx) => { installModelSelection(agentCtx) } }
|
||||
}
|
||||
const resolvedId = (await presets.resolve(presetId)).id
|
||||
return {
|
||||
agentPreset: resolvedId,
|
||||
setup: async (agentCtx) => {
|
||||
await agentCtx.plugin(ModelSwitchNotice)
|
||||
installModelSelection(agentCtx)
|
||||
await presets.mount(agentCtx, resolvedId)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ export class SessionCommandController {
|
||||
? {}
|
||||
: { reasoningEffort: resolved.reasoningEffort }),
|
||||
}
|
||||
this.ctx.agentModel.select(agent, selected)
|
||||
agent.model.select(selected)
|
||||
void this.ctx.agentDefaultModel.saveSelection(selected).catch((error: unknown) => {
|
||||
this.ctx.logger.warn(
|
||||
`session-controller: model selection changed for the Session but the default was not saved: ${String(error)}`,
|
||||
@@ -323,7 +323,7 @@ export class SessionCommandController {
|
||||
const admit = async (): Promise<SessionPromptValue> => {
|
||||
try {
|
||||
if (hasImage) {
|
||||
const current = this.ctx.agentModel.resolveForNextStep(agent)
|
||||
const current = agent.model.current()
|
||||
if (current !== undefined) {
|
||||
const model = await this.ctx.llm.resolveModelInfo(current.provider, current.model)
|
||||
if (model.inputModalities !== undefined && !model.inputModalities.includes('image')) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { hostname } from 'node:os'
|
||||
import { resolve } from 'node:path'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import type {} from '@deepseek-ai/dsh-agent-model'
|
||||
import type {} from '@deepseek-ai/dsh-fs'
|
||||
import { Context, Service } from '@deepseek-ai/cordis'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
@@ -127,7 +126,6 @@ export class SessionController extends TypertRemoteService {
|
||||
'remoteAccess',
|
||||
'sessions',
|
||||
'sessionProjections',
|
||||
'agentModel',
|
||||
'sessionQuery',
|
||||
'typert',
|
||||
'workspaceRegistry',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Browser-safe request, result, and lifecycle vocabulary for the Session Remote service. */
|
||||
|
||||
import type {} from '@deepseek-ai/dsh-agent-model/types'
|
||||
import type {} from '@deepseek-ai/dsh-agent/types'
|
||||
import type { NativeFileApplication } from '@deepseek-ai/dsh-native-command/types'
|
||||
import type {
|
||||
AttachmentIdType, ImageAttachmentLimits, ImageAttachmentRef, ImageMediaType,
|
||||
|
||||
@@ -2,7 +2,7 @@ import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { unsupportedInbox } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import { unsupportedModel, unsupportedInbox } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { Session } from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-workspace'
|
||||
@@ -31,6 +31,7 @@ async function liveAgent(ctx: Context, session: Session): Promise<Agent> {
|
||||
id: session.id,
|
||||
options: {},
|
||||
session,
|
||||
model: unsupportedModel(),
|
||||
inbox: unsupportedInbox(),
|
||||
status: 'idle',
|
||||
ctx,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { subagentIdentityProjectionDefinition } from '@deepseek-ai/dsh-subagent/
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { ApiSessionAgentController } from '../src/agent.ts'
|
||||
import { SessionCommandController } from '../src/commands.ts'
|
||||
import { createInboxStub } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import { unsupportedModel, createInboxStub } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import { installSessionReadTestServices, testSessionPersistence } from './test-remote.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-llm' {
|
||||
@@ -92,6 +92,7 @@ async function commandHarness(
|
||||
send: vi.fn(),
|
||||
inject: vi.fn(),
|
||||
session,
|
||||
model: unsupportedModel(),
|
||||
inbox,
|
||||
status: 'running',
|
||||
ctx,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AgentModel, modelSelectionProjectionDefinition } from '@deepseek-ai/dsh-agent'
|
||||
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
import { Context, FiberState } from '@deepseek-ai/cordis'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import { createInboxStub } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import * as AgentModelPlugin from '@deepseek-ai/dsh-agent-model'
|
||||
import AttachmentStore, { AttachmentId } from '@deepseek-ai/dsh-attachment'
|
||||
import type {
|
||||
FileAttachmentRef, ImageAttachmentRef, SaveFileAttachment, SaveFileStreamAttachment,
|
||||
@@ -38,7 +38,6 @@ async function uploadHarness(origin?: 'subagent'): Promise<{
|
||||
onTestFinished(() => ctx.fiber.dispose())
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(CommandRuntime)
|
||||
const session = ctx.sessions.create(SESSION, {
|
||||
@@ -46,14 +45,17 @@ async function uploadHarness(origin?: 'subagent'): Promise<{
|
||||
})
|
||||
const inbox = createInboxStub()
|
||||
const followup = vi.fn()
|
||||
const options = { provider: 'fixture', model: 'fixture-model' }
|
||||
const projection = await ctx.sessionProjections.register(modelSelectionProjectionDefinition)
|
||||
const agent = {
|
||||
id: session.id,
|
||||
options: { provider: 'fixture', model: 'fixture-model' },
|
||||
options,
|
||||
whenIdle: vi.fn(() => Promise.resolve()),
|
||||
runMaintenance: async () => { throw new Error('unexpected maintenance') },
|
||||
send: vi.fn(),
|
||||
inject: vi.fn(),
|
||||
session,
|
||||
model: new AgentModel(() => projection.stateOf(session), session, options),
|
||||
inbox,
|
||||
status: 'idle',
|
||||
ctx,
|
||||
|
||||
@@ -2,7 +2,6 @@ import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import * as AgentModelPlugin from '@deepseek-ai/dsh-agent-model'
|
||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session'
|
||||
@@ -26,7 +25,6 @@ describe('SessionController facade', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const sessionId = SessionId('controller-session')
|
||||
const header: SessionHeader = {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* metadata-only listing, Agent-free history reads, subagent ownership
|
||||
* isolation, and prompt failure mapping.
|
||||
*/
|
||||
import { AgentModel, modelSelectionProjectionDefinition } from '@deepseek-ai/dsh-agent'
|
||||
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
|
||||
import { SESSION_FORMAT_VERSION, SessionLogOffset, SessionSeq } from '@deepseek-ai/dsh-session'
|
||||
@@ -17,8 +18,7 @@ import { createUserMessage, MessageId } from '@deepseek-ai/dsh-llm'
|
||||
import { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
|
||||
import { createInboxStub, mountAgentLoopTestDependencies, mountAgentLoopTestHarness } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import type { Agent, Inbox } from '@deepseek-ai/dsh-agent'
|
||||
import * as AgentModelPlugin from '@deepseek-ai/dsh-agent-model'
|
||||
import type { SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { Session, SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import AttachmentStore from '@deepseek-ai/dsh-attachment'
|
||||
import type { SessionPromptRequest, SessionRequestId } from '../src/types.ts'
|
||||
import {
|
||||
@@ -84,7 +84,6 @@ describe('sessions.list cold merge', () => {
|
||||
try {
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const historical = header('format-historical', 100)
|
||||
const current = header('format-current', 200)
|
||||
@@ -251,7 +250,6 @@ describe('attached updatedAt tracks human prompts', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const remote = await createSessionTestRemote(ctx, { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' })
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
@@ -397,7 +395,6 @@ describe('Remote Agent and Session lookup policy', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const remote = await createSessionTestRemote(ctx, {
|
||||
defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
|
||||
@@ -419,7 +416,6 @@ describe('Remote Agent and Session lookup policy', () => {
|
||||
await ctx.plugin(TypertRegistry)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const sessionId = sid('session-remote-cold')
|
||||
const meta = header(sessionId, 1000)
|
||||
@@ -461,7 +457,6 @@ describe('Remote Agent and Session lookup policy', () => {
|
||||
await ctx.plugin(TypertRegistry)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const coldId = sid('session-remote-cold-child')
|
||||
const coldMeta = header(coldId, 1000, {
|
||||
@@ -507,7 +502,6 @@ describe('Remote Agent and Session lookup policy', () => {
|
||||
await ctx.plugin(TypertRegistry)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const sessionId = sid('session-remote-resumed-child')
|
||||
const meta = header(sessionId, 1000)
|
||||
@@ -544,7 +538,6 @@ describe('subagent ownership fence', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const sessionId = sid('session-child')
|
||||
const meta = header('session-child', 1000, {
|
||||
@@ -624,7 +617,6 @@ describe('subagent ownership fence', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const sessionId = sid('session-legacy-child')
|
||||
const meta = header('session-legacy-child', 1000, {
|
||||
@@ -663,7 +655,6 @@ describe('subagent ownership fence', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const parentSession = ctx.sessions.create(sid('session-parent'), { meta: { cwd: '/proj' } })
|
||||
const parent = { id: parentSession.id, session: parentSession, status: 'idle', ctx } as Agent
|
||||
@@ -724,7 +715,6 @@ describe('subagent ownership fence', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const session = ctx.sessions.create(sid('session-ordinary-fork'), {
|
||||
seed: [{
|
||||
@@ -737,9 +727,14 @@ describe('subagent ownership fence', () => {
|
||||
inheritedEventCount: SessionLogOffset(1),
|
||||
})
|
||||
const followup = vi.fn()
|
||||
const agent = {
|
||||
id: session.id, session, inbox: inboxFor(), status: 'idle', ctx, followup,
|
||||
} as unknown as Agent
|
||||
const agent: Agent = {
|
||||
id: session.id, session, model: await modelFor(ctx, session),
|
||||
options: { provider: 'p', model: 'm' },
|
||||
inbox: inboxFor(), status: 'idle', ctx, followup,
|
||||
cancel: vi.fn(), whenIdle: () => Promise.resolve(),
|
||||
runMaintenance: async () => { throw new Error('unexpected maintenance') },
|
||||
send: vi.fn(), inject: vi.fn(), steer: vi.fn(),
|
||||
}
|
||||
await ctx.agents.register(agent)
|
||||
const remote = await createSessionTestRemote(ctx, { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' })
|
||||
|
||||
@@ -756,13 +751,17 @@ describe('subagent ownership fence', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const session = ctx.sessions.create(sid('session-browser-zone'), { meta: { cwd: '/proj' } })
|
||||
const followup = vi.fn()
|
||||
const agent = {
|
||||
id: session.id, session, inbox: inboxFor(), status: 'idle', ctx, followup,
|
||||
} as unknown as Agent
|
||||
const agent: Agent = {
|
||||
id: session.id, session, model: await modelFor(ctx, session),
|
||||
options: { provider: 'p', model: 'm' },
|
||||
inbox: inboxFor(), status: 'idle', ctx, followup,
|
||||
cancel: vi.fn(), whenIdle: () => Promise.resolve(),
|
||||
runMaintenance: async () => { throw new Error('unexpected maintenance') },
|
||||
send: vi.fn(), inject: vi.fn(), steer: vi.fn(),
|
||||
}
|
||||
await ctx.agents.register(agent)
|
||||
const remote = await createSessionTestRemote(ctx, {
|
||||
defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
|
||||
@@ -829,7 +828,6 @@ describe('degenerate composition (no persistence, no factory)', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const remote = await createSessionTestRemote(ctx, { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' })
|
||||
|
||||
@@ -852,7 +850,6 @@ describe('degenerate composition (no persistence, no factory)', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const inspect = vi.fn()
|
||||
const stat = vi.fn(() => Promise.resolve(undefined))
|
||||
@@ -875,25 +872,31 @@ describe('degenerate composition (no persistence, no factory)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
async function modelFor(ctx: Context, session: Session): Promise<AgentModel> {
|
||||
const projection = await ctx.sessionProjections.register(modelSelectionProjectionDefinition)
|
||||
return new AgentModel(() => projection.stateOf(session), session, { provider: 'p', model: 'm' })
|
||||
}
|
||||
|
||||
describe('sessions.prompt synchronous rejection', () => {
|
||||
it('rejects content without non-whitespace text or an attachment before delivery or Session events', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const session = ctx.sessions.create(sid('session-empty-prompt'))
|
||||
const options = { provider: 'p', model: 'm' }
|
||||
const followup = vi.fn()
|
||||
const steer = vi.fn()
|
||||
await ctx.agents.register({
|
||||
id: session.id,
|
||||
options: { provider: 'p', model: 'm' },
|
||||
options,
|
||||
whenIdle: vi.fn(() => Promise.resolve()),
|
||||
runMaintenance: async () => { throw new Error('unexpected maintenance') },
|
||||
send: vi.fn(),
|
||||
inject: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
session,
|
||||
model: await modelFor(ctx, session),
|
||||
inbox: inboxFor(),
|
||||
status: 'idle',
|
||||
ctx,
|
||||
@@ -984,7 +987,6 @@ describe('sessions.prompt synchronous rejection', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const session = ctx.sessions.create(sid('session-throwing'))
|
||||
// A live structural stub whose delivery verbs throw synchronously, the
|
||||
@@ -992,12 +994,17 @@ describe('sessions.prompt synchronous rejection', () => {
|
||||
await ctx.agents.register({
|
||||
id: session.id,
|
||||
session,
|
||||
model: await modelFor(ctx, session),
|
||||
inbox: inboxFor(),
|
||||
status: 'idle',
|
||||
ctx,
|
||||
options: { provider: 'p', model: 'm' },
|
||||
cancel: vi.fn(), whenIdle: () => Promise.resolve(),
|
||||
runMaintenance: async () => { throw new Error('unexpected maintenance') },
|
||||
send: vi.fn(), inject: vi.fn(),
|
||||
followup: () => { throw new Error('agent "session-throwing" lifecycle disposed') },
|
||||
steer: () => { throw new Error('agent "session-throwing" lifecycle disposed') },
|
||||
} as unknown as Agent)
|
||||
})
|
||||
const remote = await createSessionTestRemote(ctx, { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' })
|
||||
|
||||
for (const mode of ['queue', 'steer'] as const) {
|
||||
@@ -1019,7 +1026,6 @@ describe('sessions.prompt synchronous rejection', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const sessionId = sid('race-resume')
|
||||
const meta: SessionHeader = header('race-resume', 1000)
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Context } from '@deepseek-ai/cordis'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent, AgentHandle, CreateAgentOptions } from '@deepseek-ai/dsh-agent'
|
||||
import { mountAgentLoopTestDependencies, mountAgentLoopTestHarness } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import * as AgentModelPlugin from '@deepseek-ai/dsh-agent-model'
|
||||
import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic'
|
||||
import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
import { ToolCallId, createMessage, createUserMessage, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
|
||||
@@ -34,7 +33,6 @@ async function composed(workspaces: readonly Workspace[] = []): Promise<Context>
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt, { personaPrefix: '' })
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
installSessionReadTestServices(ctx)
|
||||
ctx.provide('workspaceRegistry', { list: () => workspaces, archivedSessionIds: [] } as never)
|
||||
@@ -665,7 +663,7 @@ describe('sessions.fork', () => {
|
||||
const child = ctx.agents.get(response.value.sessionId)
|
||||
if (child === undefined) throw new Error('fork did not publish the child agent')
|
||||
expect(child.session.snapshotEvents().slice(0, child.session.inheritedEventCount)).toEqual(inherited)
|
||||
expect(ctx.agentModel.resolveForNextStep(child)).toEqual({
|
||||
expect(child.model.current()).toEqual({
|
||||
provider: 'inherited-provider', model: 'inherited-model', reasoningEffort: 'high',
|
||||
})
|
||||
await ctx.fiber.dispose()
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* catalog injection, unavailable-model refusals, and the prompt-assembly
|
||||
* boundary for a running selection change.
|
||||
*/
|
||||
import { AgentModel, modelSelectionProjectionDefinition } from '@deepseek-ai/dsh-agent'
|
||||
import { createInboxStub } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
|
||||
@@ -11,7 +12,6 @@ import { describe, expect, it, vi, onTestFinished } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import * as AgentModelPlugin from '@deepseek-ai/dsh-agent-model'
|
||||
import AttachmentStore from '@deepseek-ai/dsh-attachment'
|
||||
import LlmRuntime, { LlmAdapter, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
|
||||
import type {
|
||||
@@ -99,7 +99,6 @@ async function harness(logged?: {
|
||||
await ctx.plugin(SystemPrompt, { personaPrefix: '' })
|
||||
await ctx.plugin(LlmRuntime)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
ctx.llm.registerAdapter(['deepseek-official'], new CatalogAdapter('DeepSeek', [
|
||||
{ provider: 'deepseek-official', id: 'deepseek-chat', name: 'DeepSeek Chat' },
|
||||
@@ -128,13 +127,16 @@ async function harness(logged?: {
|
||||
reason: 'initial',
|
||||
})
|
||||
}
|
||||
const projection = await ctx.sessionProjections.register(modelSelectionProjectionDefinition)
|
||||
const options = {
|
||||
get provider() { return ctx.agentDefaultModel.currentSelection().provider },
|
||||
get model() { return ctx.agentDefaultModel.currentSelection().model },
|
||||
get reasoningEffort() { return ctx.agentDefaultModel.currentSelection().reasoningEffort },
|
||||
}
|
||||
const agent = {
|
||||
id: session.id,
|
||||
options: {
|
||||
get provider() { return ctx.agentDefaultModel.currentSelection().provider },
|
||||
get model() { return ctx.agentDefaultModel.currentSelection().model },
|
||||
get reasoningEffort() { return ctx.agentDefaultModel.currentSelection().reasoningEffort },
|
||||
},
|
||||
options,
|
||||
model: new AgentModel(() => projection.stateOf(session), session, options),
|
||||
session,
|
||||
status: 'running',
|
||||
ctx,
|
||||
@@ -520,7 +522,7 @@ describe('Web session model selection', () => {
|
||||
model: 'deepseek-reasoner',
|
||||
reasoningEffort: 'max',
|
||||
})
|
||||
expect(ctx.agentModel.resolveForNextStep(agent)).toEqual(selected.selected)
|
||||
expect(agent.model.current()).toEqual(selected.selected)
|
||||
expect(agent.session.snapshotEvents().at(-1)).toMatchObject({
|
||||
type: 'model/selection', data: selected.selected,
|
||||
})
|
||||
@@ -581,7 +583,10 @@ describe('Web session model selection', () => {
|
||||
ctx.agents.setFactory({
|
||||
async createAgent(ownerCtx, options) {
|
||||
const session = ctx.sessions.create(options.sessionId)
|
||||
const agent: Agent = { ...template, id: session.id, session, ctx: ownerCtx, options: options.agentOptions ?? {} }
|
||||
const agentOptions = options.agentOptions ?? {}
|
||||
const projection = await ownerCtx.sessionProjections.register(modelSelectionProjectionDefinition)
|
||||
const model = new AgentModel(() => projection.stateOf(session), session, agentOptions)
|
||||
const agent: Agent = { ...template, id: session.id, session, ctx: ownerCtx, options: agentOptions, model }
|
||||
await options.setup?.(ownerCtx, agent)
|
||||
await ctx.agents.register(agent)
|
||||
return { agent, dispose: () => Promise.resolve() }
|
||||
@@ -591,9 +596,9 @@ describe('Web session model selection', () => {
|
||||
const first = expectValue(await remote.create({ cwd: '/tmp' }))
|
||||
const second = expectValue(await remote.create({ cwd: '/tmp' }))
|
||||
const agent = ctx.agents.get(first.sessionId)!
|
||||
expect(ctx.agentModel.resolveForNextStep(agent)).toEqual(stored)
|
||||
expect(agent.model.current()).toEqual(stored)
|
||||
stored = { provider: 'deepseek-official', model: 'deepseek-reasoner' }
|
||||
expect(ctx.agentModel.resolveForNextStep(agent)).toEqual(stored)
|
||||
expect(agent.model.current()).toEqual(stored)
|
||||
const selection = { ...stored, reasoningEffort: ReasoningEffortId('high') }
|
||||
// The explicit selection is identical to the live fallback, including effort.
|
||||
stored = selection
|
||||
@@ -601,7 +606,7 @@ describe('Web session model selection', () => {
|
||||
expect(agent.session.snapshotEvents().filter(event => event.type === 'model/selection'))
|
||||
.toMatchObject([{ data: selection }])
|
||||
expectValue(await remote.selectModel({ sessionId: second.sessionId, provider: 'deepseek-official', model: 'deepseek-chat' }))
|
||||
expect(ctx.agentModel.resolveForNextStep(agent)).toEqual(selection)
|
||||
expect(agent.model.current()).toEqual(selection)
|
||||
expect(currentSelection(ctx, first.sessionId)).toEqual(selection)
|
||||
})
|
||||
|
||||
@@ -634,7 +639,7 @@ describe('Web session model selection', () => {
|
||||
cwd: '/tmp',
|
||||
})
|
||||
|
||||
expect(ctx.agentModel.resolveForNextStep(agent))
|
||||
expect(agent.model.current())
|
||||
.toEqual({ provider: 'deepseek-official', model: 'deepseek-chat' })
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
mountAgentLoopTestDependencies,
|
||||
mountAgentLoopTestHarness,
|
||||
} from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import * as AgentModelPlugin from '@deepseek-ai/dsh-agent-model'
|
||||
import { createSessionTestRemote, testSessionPersistence, type TestSessionRemote } from './test-remote.ts'
|
||||
|
||||
const ownedContexts = new Set<Context>()
|
||||
@@ -163,7 +162,6 @@ describe('session.history projections block', () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const parent = ctx.sessions.create(SessionId('wire-seed-parent'), { meta: { cwd: '/workspace' } })
|
||||
parent.append('turn/start', { turn: 1 })
|
||||
@@ -581,7 +579,6 @@ describe('session.list projections column', () => {
|
||||
await ctx.plugin(StorageDomain, { backend: 'json' })
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.sessionProjections.register(privatePromptUnit())
|
||||
await ctx.plugin(SessionProjectionCache, { writeEveryEvents: 100, writeIntervalMs: 60_000 })
|
||||
@@ -637,7 +634,6 @@ describe('session.list projections column', () => {
|
||||
await ctx.plugin(StorageDomain, { backend: 'json' })
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionProjectionRegistry)
|
||||
await ctx.plugin(AgentModelPlugin)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.sessionProjections.register(titleProjectionDefinition)
|
||||
await ctx.plugin(SessionProjectionCache, { writeEveryEvents: 100, writeIntervalMs: 60_000 })
|
||||
|
||||
@@ -11,65 +11,24 @@
|
||||
"src/remote-events.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../gateway/tsconfig.client.json"
|
||||
},
|
||||
{
|
||||
"path": "../../attachment/attachment"
|
||||
},
|
||||
{
|
||||
"path": "../../client/connection/tsconfig.client.json"
|
||||
},
|
||||
{
|
||||
"path": "../../client/file-upload/tsconfig.client.json"
|
||||
},
|
||||
{
|
||||
"path": "../../client/store"
|
||||
},
|
||||
{
|
||||
"path": "../../context/file-reference"
|
||||
},
|
||||
{
|
||||
"path": "../../core/session"
|
||||
},
|
||||
{
|
||||
"path": "../../jobs/jobs"
|
||||
},
|
||||
{
|
||||
"path": "../../llm/llm"
|
||||
},
|
||||
{
|
||||
"path": "../../session/session-projection"
|
||||
},
|
||||
{
|
||||
"path": "../../session/session-title"
|
||||
},
|
||||
{
|
||||
"path": "../../subagent/subagent"
|
||||
},
|
||||
{
|
||||
"path": "../../util/brand"
|
||||
},
|
||||
{
|
||||
"path": "../../util/crypto"
|
||||
},
|
||||
{
|
||||
"path": "../../util/workspace-path"
|
||||
},
|
||||
{
|
||||
"path": "../../workspace/workspace"
|
||||
},
|
||||
{
|
||||
"path": "../../typert/protocol"
|
||||
},
|
||||
{
|
||||
"path": "../../util/native-command"
|
||||
},
|
||||
{
|
||||
"path": "../../core/agent-model"
|
||||
}
|
||||
{ "path": "../../../vendor/cordis" },
|
||||
{ "path": "../gateway/tsconfig.client.json" },
|
||||
{ "path": "../../attachment/attachment" },
|
||||
{ "path": "../../client/connection/tsconfig.client.json" },
|
||||
{ "path": "../../client/file-upload/tsconfig.client.json" },
|
||||
{ "path": "../../client/store" },
|
||||
{ "path": "../../context/file-reference" },
|
||||
{ "path": "../../core/session" },
|
||||
{ "path": "../../jobs/jobs" },
|
||||
{ "path": "../../llm/llm" },
|
||||
{ "path": "../../session/session-projection" },
|
||||
{ "path": "../../session/session-title" },
|
||||
{ "path": "../../subagent/subagent" },
|
||||
{ "path": "../../util/brand" },
|
||||
{ "path": "../../util/crypto" },
|
||||
{ "path": "../../util/workspace-path" },
|
||||
{ "path": "../../workspace/workspace" },
|
||||
{ "path": "../../typert/protocol" },
|
||||
{ "path": "../../util/native-command" }
|
||||
]
|
||||
}
|
||||
@@ -114,9 +114,6 @@
|
||||
},
|
||||
{
|
||||
"path": "../../session/working-directory"
|
||||
},
|
||||
{
|
||||
"path": "../../core/agent-model"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user