refactor(web): consume owner remote events directly
This commit is contained in:
@@ -10,6 +10,8 @@ When the Host reports that no adapter serves the session's route (`session.model
|
||||
|
||||
Directories are per-session, resolved lazily through `ctx.models.directoryFor(sessionId)`, and disposed with the session scope. Addressed subagent sessions expose neither entry, and their directory rejects loads, selections, and reconnect refreshes, because ordinary Agent-bound model RPCs would activate persisted child history outside the direct-parent continuation path.
|
||||
|
||||
Every resident directory refetches directly on forwarded `llm/adapters-updated` and `settings/document-updated` owner events. Provider topology, provider catalogs, and the default selection therefore converge without the Host or client runtime deriving a separate model-change alias.
|
||||
|
||||
The `/client` exports are the plugin body (`apply`/`inject`), `ModelService`, `ModelDirectory` with its state fields, and the seat's injected face type.
|
||||
|
||||
## Model Experience
|
||||
|
||||
@@ -10,6 +10,8 @@ Host 报告的 `ModelSelection` 是唯一的选择事实,其中包含提供方
|
||||
|
||||
目录按会话惰性解析(`ctx.models.directoryFor(sessionId)`),随会话作用域一并释放。已寻址 subagent 会话不公开任一入口,其目录会拒绝加载、选择与重新连接刷新,因为绑定到 agent(智能体)的普通模型 RPC 会在直接 parent 继续执行路径之外激活持久化 child 历史。
|
||||
|
||||
每一份常驻目录都会直接在转发的 owner 事件 `llm/adapters-updated` 与 `settings/document-updated` 上重拉。因此提供方拓扑、提供方目录与默认选择都能收敛,Host 与 client runtime 无需再派生一个单独的模型变更别名。
|
||||
|
||||
`/client` 导出面为插件本体(`apply`/`inject`)、`ModelService`、`ModelDirectory` 及其状态形状、slot 注入面类型。
|
||||
|
||||
## 模型体验
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
"inject": [
|
||||
"@deepseek-ai/dsh-client-locale",
|
||||
"@deepseek-ai/dsh-client-runtime",
|
||||
"@deepseek-ai/dsh-client-ui-command"
|
||||
"@deepseek-ai/dsh-client-ui-command",
|
||||
"@deepseek-ai/dsh-api-remotes"
|
||||
],
|
||||
"platform": "web"
|
||||
}
|
||||
@@ -45,6 +46,7 @@
|
||||
},
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
@@ -59,9 +61,11 @@
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-command": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
* history outside the direct-parent continuation path.
|
||||
*/
|
||||
import type { ModelSelection, SessionModels } from '@deepseek-ai/dsh-client-connection/client'
|
||||
// Type-only: pulls the forwarded Host-event face and ctx.remote merge.
|
||||
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { CommandServiceContract, SelectOption } from '@deepseek-ai/dsh-client-ui-command/client'
|
||||
// Type-only: pulls the ui-conversation SlotMap merge (the input.model seat).
|
||||
@@ -96,7 +98,7 @@ function selectionOf(state: ModelDirectoryState, id: string): ModelSelection | u
|
||||
const NS = 'model'
|
||||
|
||||
/** Required services: the contribution registry, the seat's slot registry, locale, and the service's own faces. */
|
||||
export const inject = ['command', 'connection', 'locale', 'sessions', 'slots']
|
||||
export const inject = ['command', 'connection', 'locale', 'sessions', 'slots', 'remote']
|
||||
|
||||
/**
|
||||
* Client plugin body: mount ModelService, register the `model` dictionaries,
|
||||
|
||||
@@ -32,7 +32,7 @@ interface LiveState {
|
||||
|
||||
/** The `ctx.models` session model-selection service. */
|
||||
export class ModelService extends Service {
|
||||
static inject = ['connection', 'sessions']
|
||||
static inject = ['connection', 'sessions', 'remote']
|
||||
|
||||
private readonly live: LiveState = { directories: new Map() }
|
||||
|
||||
@@ -49,14 +49,15 @@ export class ModelService extends Service {
|
||||
ctx.on('connection/reset', () => {
|
||||
for (const directory of this.live.directories.values()) directory.resetConnected()
|
||||
})
|
||||
// Provider topology changed on the host (a settings-born route appeared
|
||||
// or dropped): refresh every open directory in the background so pickers
|
||||
// show the new catalog without a reopen. Failures stay on each store.
|
||||
ctx.on('models/changed', () => {
|
||||
// Either source can change the directory: registry topology commits and
|
||||
// settings documents that carry provider catalogs or default selection.
|
||||
const refresh = (): void => {
|
||||
for (const directory of this.live.directories.values()) {
|
||||
directory.load().catch(() => undefined)
|
||||
}
|
||||
})
|
||||
}
|
||||
ctx.remote.$on('llm/adapters-updated', refresh)
|
||||
ctx.remote.$on('settings/document-updated', refresh)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ 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 { TestRemote } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import type { ModelSelection } 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'
|
||||
@@ -112,6 +113,7 @@ async function bench() {
|
||||
? { parentSessionId: sid('parent'), childSessionId: id, mode: 'continuable' as const }
|
||||
: undefined,
|
||||
})
|
||||
new TestRemote(ctx)
|
||||
const fiber = ctx.plugin({ inject: [...inject], apply })
|
||||
await fiber.await()
|
||||
await ctx.plugin(function probe() {}).await()
|
||||
@@ -245,14 +247,14 @@ describe('ui-model dual entry', () => {
|
||||
expect(b.blockOf('s1')).toBeUndefined()
|
||||
|
||||
b.setRoutable(false)
|
||||
b.ctx.emit('models/changed')
|
||||
b.ctx.remote.$dispatch('llm/adapters-updated', [])
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
expect(b.blockOf('s1')?.reason).toBe(zh['blocked.composer'])
|
||||
|
||||
// Recovering clears it without a reload of the surface.
|
||||
b.setRoutable(true)
|
||||
b.ctx.emit('models/changed')
|
||||
b.ctx.remote.$dispatch('settings/document-updated', ['llm-deepseek', 1])
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
expect(b.blockOf('s1')).toBeUndefined()
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../support/invariants"
|
||||
},
|
||||
{
|
||||
"path": "../../api/remotes/tsconfig.client.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user