diff --git a/packages/api/remotes/src/client/index.ts b/packages/api/remotes/src/client/index.ts index 8638c54b9f..d40e9f65fc 100644 --- a/packages/api/remotes/src/client/index.ts +++ b/packages/api/remotes/src/client/index.ts @@ -5,8 +5,8 @@ import agentPresetsRemote from '@deepseek-ai/dsh-agent-presets/remote' import commandsRemote from '@deepseek-ai/dsh-commands/remote' import settingsControllerRemote from '@deepseek-ai/dsh-api-settings-controller/remote' import goalsRemote from '@deepseek-ai/dsh-goal/remote' +import llmRemote from '@deepseek-ai/dsh-llm/remote' import dynamicRemote from '@deepseek-ai/dsh-cordis-host-runner/remote' -import fileReferencesRemote from '@deepseek-ai/dsh-file-reference/remote' import pluginInventoryRemote from '@deepseek-ai/dsh-host-plugin-inventory/remote' import messageFeedbackRemote from '@deepseek-ai/dsh-message-feedback/remote' import sessionReferencesRemote from '@deepseek-ai/dsh-session-reference/remote' @@ -20,8 +20,8 @@ export type { PluginInventorySnapshot } from '@deepseek-ai/dsh-host-plugin-inven export type {} from '@deepseek-ai/dsh-agent-presets/remote' export type {} from '@deepseek-ai/dsh-commands/remote' export type {} from '@deepseek-ai/dsh-api-settings-controller/remote' -export type {} from '@deepseek-ai/dsh-file-reference/remote' export type {} from '@deepseek-ai/dsh-goal/remote' +export type {} from '@deepseek-ai/dsh-llm/remote' export type {} from '@deepseek-ai/dsh-host-plugin-inventory/remote' export type {} from '@deepseek-ai/dsh-message-feedback/remote' export type {} from '@deepseek-ai/dsh-session-reference/remote' @@ -54,11 +54,10 @@ export type {} from '@deepseek-ai/dsh-api-session-controller/types' * the carrier's runtime values stay behind their own module edge. */ export type { - ConfigurableProviderView, ConnectionHandle, ConnectionSinks, ContentBlock, - DiscoveredModelView, IApiClient, - MessageId, ModelCatalog, ModelCatalogFailure, ModelProviderGroup, ModelReasoningEffort, ModelSelection, + ConnectionHandle, ConnectionSinks, ContentBlock, IApiClient, + MessageId, ModelCatalogFailure, ModelProviderGroup, ModelReasoningEffort, ModelSelection, RpcError, RpcId, RpcRequest, RpcResponse, RpcResult, SessionId, - SkillEntry, StreamChunk, + StreamChunk, } from '@deepseek-ai/dsh-client-connection/client' export type {} from '@deepseek-ai/dsh-api-gateway/client' export type {} from '@deepseek-ai/dsh-cordis-host-runner/remote' @@ -111,6 +110,11 @@ export type { CredentialInfo } from '@deepseek-ai/dsh-credentials/types' export type { SettingsDescribeValue, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView, } from '@deepseek-ai/dsh-settings/types' +// Provider registry and discovery vocabulary for the llm namespace. +export type { + LlmConfigurableProvider, LlmDiscoveredModel, LlmModelDiscoveryError, + LlmModelDiscoveryRequest, LlmProviderInfo, +} from '@deepseek-ai/dsh-llm/types' // Reference-discovery result vocabulary for the fileReferences and // sessionReferenceResolver namespaces. export type { FileReferenceCandidate } from '@deepseek-ai/dsh-file-reference/types' @@ -123,6 +127,7 @@ export type ClientFailure = | import('@deepseek-ai/dsh-api-session-controller/types').SessionError | import('@deepseek-ai/dsh-api-settings-controller/types').CredentialError | import('@deepseek-ai/dsh-api-settings-controller/types').SettingsError + | import('@deepseek-ai/dsh-llm/types').LlmModelDiscoveryError | import('@deepseek-ai/dsh-subagent/client').SubagentControlError | import('@deepseek-ai/dsh-api-workspace-controller/types').WorkspaceError @@ -150,8 +155,7 @@ export async function apply(ctx: Context): Promise<() => Promise> { const disposers: Array<() => Promise> = [] try { for (const contribution of [ - agentPresetsRemote, commandsRemote, settingsControllerRemote, goalsRemote, dynamicRemote, - fileReferencesRemote, + agentPresetsRemote, commandsRemote, settingsControllerRemote, goalsRemote, llmRemote, dynamicRemote, pluginInventoryRemote, messageFeedbackRemote, sessionReferencesRemote, subagentsRemote, sessionRemote, workspaceRemote, ]) { diff --git a/packages/client/connection/src/client/api.ts b/packages/client/connection/src/client/api.ts index 4d4826da79..0ae8af9ddf 100644 --- a/packages/client/connection/src/client/api.ts +++ b/packages/client/connection/src/client/api.ts @@ -8,11 +8,8 @@ export type { ApiProxy, HostApi, ResponseValue, - SkillsApi, SkillEntry, ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning, ModelReasoningEffort, ModelSelection, - SettingsApi, - ConfigurableProviderView, DiscoveredModelView, LlmApi, } from '@deepseek-ai/dsh-host-apiproxy/api' export type { RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode, diff --git a/packages/client/connection/src/client/fixture.ts b/packages/client/connection/src/client/fixture.ts index 6cc57082cb..a5689069c2 100644 --- a/packages/client/connection/src/client/fixture.ts +++ b/packages/client/connection/src/client/fixture.ts @@ -551,7 +551,7 @@ const OPENAI_REASONING = { defaultEffort: 'medium', } -/** Catalog served by `llm.models` (fresh copies per call). */ +/** Catalog served by `session/modelCatalog` (fresh copies per call). */ function fixtureModelGroups(): ModelProviderGroup[] { return [ { @@ -1834,6 +1834,25 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld { }, } }, + openSettingsDocument(): RpcResult<{ opened: true }> { + return { ok: true, value: { opened: true } } + }, + openAgentPresetDirectory(agentPreset: string): RpcResult< + { opened: true } | { opened: false; path: string } + > { + const existing = fixturePresets.get(agentPreset) + if (existing === undefined || existing.trust === 'system') { + return { + ok: false, + error: { + code: 'agent-preset-read-only', + message: `agent preset "${agentPreset}" ships with the deployment`, + details: { agentPreset, reason: 'it ships with the deployment' }, + }, + } + } + return { ok: true, value: { opened: true } } + }, } const credentialRemotes = { @@ -1999,10 +2018,6 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld { function ok(request: RpcRequest

, value: T): Promise> { return Promise.resolve({ rpcId: request.rpcId, result: { ok: true, value } }) } - function err(request: RpcRequest

, error: Extract, { ok: false }>['error']): Promise> { - return Promise.resolve({ rpcId: request.rpcId, result: { ok: false, error } }) - } - function sessionOk(value: T): Promise> { return Promise.resolve({ ok: true, value }) } @@ -2012,16 +2027,6 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld { } const summaryOf = (id: SessionId): FixtureSessionSummary | undefined => sessions.find(s => s.sessionId === id) - /** Shared session guard for sessionId-addressed catalog routes: the error - * response when the session is unknown, undefined when it exists. */ - const requireSession = (request: RpcRequest<{ sessionId: SessionId }>): Promise> | undefined => { - if (summaryOf(request.payload.sessionId) !== undefined) return undefined - return err<{ sessionId: SessionId }, never>(request, { - code: 'session-not-found', - message: `no session ${request.payload.sessionId}`, - details: { sessionId: request.payload.sessionId }, - }) - } const requireRemoteSession = ( request: { readonly sessionId: SessionId }, ): Promise> | undefined => { @@ -3398,65 +3403,6 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld { describe: request => ok(request, { version: '0.0.0-fixture', cwd: '/tmp/fixture', attachedSessions, home: FIXTURE_HOME, canOpenPath: true, }), - openPath: request => ok(request, { opened: true as const }), - }, - agentPresets: { - // Native opens are deterministic no-op successes in this fixture, so the - // open-directory affordance renders and the path-text fallback stays a - // component-test concern. - openDocument: (request) => { - const { agentPreset } = request.payload - const existing = fixturePresets.get(agentPreset) - if (existing === undefined || existing.trust === 'system') { - return err(request, { - code: 'agent-preset-read-only', - message: `agent preset "${agentPreset}" ships with the deployment`, - details: { agentPreset, reason: 'it ships with the deployment' }, - }) - } - return ok(request, { opened: true as const }) - }, - }, - - skills: { - list: (request) => { - const missing = requireSession(request) - if (missing !== undefined) return missing - return ok(request, { - skills: [ - { name: 'fixture-demo', description: 'fixture 技能样本', whenToUse: '仅供 UI 目录渲染验收', modelInvocable: true }, - { name: 'fixture-user-only', description: 'fixture 仅用户技能样本', modelInvocable: false }, - ], - }) - }, - }, - settings: { - // Native opens are deterministic no-op successes in this fixture, as is host.openPath. - openDocument: request => ok(request, { opened: true as const }), - }, - llm: { - providers: request => ok(request, { - providers: [ - { provider: 'deepseek-official', displayName: 'DeepSeek', settingsNs: 'llm-deepseek', settingsPath: [], active: true }, - { provider: 'openai', displayName: 'openai', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'openai'], active: true, declared: false }, - { provider: 'anthropic', displayName: 'anthropic', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'anthropic'], active: false, declared: false }, - // One hand-declared route, so a surface reading this fixture meets - // the tagged shape rather than only the shipped one. - { provider: 'acme-gateway', displayName: 'Acme Gateway', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'acme-gateway'], active: true, declared: true }, - ], - }), - models: request => ok(request, { - default: { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, - routableProviders: ['deepseek-official', 'openai', 'acme-gateway'], - groups: fixtureModelGroups(), - failures: [], - }), - // The fixture endpoint is imaginary, so the interrogation answers the - // catalog it already serves — enough for a surface to exercise adopting - // candidates without a reachable provider. - discoverModels: request => ok(request, { - models: fixtureModelGroups().flatMap(group => group.models.map(model => ({ id: model.id, name: model.name }))), - }), }, // Satisfies the ApiProxy contract type only: the browser export button // hands GET /api/session.export to the native download manager, so this @@ -3484,6 +3430,7 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld { refs?: readonly string[] value?: string ns?: string + settingsNs?: string agentPreset?: string from?: string id?: string @@ -3538,6 +3485,58 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld { case 'credentials/set': return Promise.resolve(credentialRemotes.set(args.ref as string)) case 'credentials/unset': return Promise.resolve(credentialRemotes.unset(args.ref as string)) case 'settings/describe': return Promise.resolve(settingsRemotes.describe()) + case 'settings/openSettingsDocument': return Promise.resolve(settingsRemotes.openSettingsDocument()) + case 'settings/openAgentPresetDirectory': return Promise.resolve( + settingsRemotes.openAgentPresetDirectory(args.agentPreset as string), + ) + case 'skills/list': { + const skillRequest = request as { readonly sessionId: SessionId } + const missing = requireRemoteSession(skillRequest) + if (missing !== undefined) return missing + return sessionOk({ + skills: [ + { name: 'fixture-demo', description: 'fixture 技能样本', whenToUse: '仅供 UI 目录渲染验收', modelInvocable: true }, + { name: 'fixture-user-only', description: 'fixture 仅用户技能样本', modelInvocable: false }, + ], + }) + } + case 'session/openWorkspacePath': { + const pathRequest = request as { readonly sessionId: SessionId; readonly path: string } + const missing = requireRemoteSession(pathRequest) + return missing ?? sessionOk({ opened: true as const }) + } + case 'session/modelCatalog': return Promise.resolve({ + ok: true, + value: { + default: { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, + routableProviders: ['deepseek-official', 'openai', 'acme-gateway'], + groups: fixtureModelGroups(), + failures: [], + }, + }) + case 'llm/listProviders': return Promise.resolve({ + ok: true, + value: [ + { id: 'deepseek-official', name: 'DeepSeek' }, + { id: 'openai', name: 'openai' }, + { id: 'acme-gateway', name: 'Acme Gateway' }, + ], + }) + case 'llm/listConfigurableProviders': return Promise.resolve({ + ok: true, + value: [ + { provider: 'deepseek-official', displayName: 'DeepSeek', settingsNs: 'llm-deepseek', settingsPath: [] }, + { provider: 'openai', displayName: 'openai', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'openai'], declared: false }, + { provider: 'anthropic', displayName: 'anthropic', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'anthropic'], declared: false }, + { provider: 'acme-gateway', displayName: 'Acme Gateway', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'acme-gateway'], declared: true }, + ], + }) + // The fixture endpoint is imaginary, so interrogation answers the + // catalog it already serves without a network request. + case 'llm/discoverModels': return Promise.resolve({ + ok: true, + value: fixtureModelGroups().flatMap(group => group.models.map(model => ({ id: model.id, name: model.name }))), + }) case 'settings/update': return Promise.resolve(settingsRemotes.update(args.ns as string)) case 'settings/replace': return Promise.resolve(settingsRemotes.replace(args.ns as string)) case 'settings/mutate': return Promise.resolve(settingsRemotes.mutate(args.ns as string)) @@ -3643,13 +3642,13 @@ export class FixtureApiClient extends AbstractApiClient { payload: RequestPayload, signal?: AbortSignal, ): Promise>> { + void signal const request = rpcRequest(payload) const full: ClientRequest = { type: 'client-request', rpcId: request.rpcId, method, payload } this.onEnvelope(full) const response = await this.dispatch( method, request as RpcRequest, - signal ?? new AbortController().signal, ) as RpcResponse> const fullResponse: ServerResponse = { type: 'server-response', rpcId: response.rpcId, result: response.result } this.onEnvelope(fullResponse) @@ -3660,17 +3659,9 @@ export class FixtureApiClient extends AbstractApiClient { private dispatch( method: keyof RpcMethodMap, request: RpcRequest, - signal: AbortSignal, ): Promise> { switch (method) { case 'host.describe': return this.api.host.describe(request) - case 'host.openPath': return this.api.host.openPath(request, new AbortController().signal) - case 'skill.list': return this.api.skills.list(request) - case 'agentPreset.openDocument': return this.api.agentPresets.openDocument(request, new AbortController().signal) - case 'settings.openDocument': return this.api.settings.openDocument(request, signal) - case 'llm.providers': return this.api.llm.providers(request) - case 'llm.models': return this.api.llm.models(request) - case 'llm.discoverModels': return this.api.llm.discoverModels(request, signal) } } diff --git a/packages/client/connection/src/client/index.ts b/packages/client/connection/src/client/index.ts index c8b66fbc77..a24d014496 100644 --- a/packages/client/connection/src/client/index.ts +++ b/packages/client/connection/src/client/index.ts @@ -31,14 +31,11 @@ declare module '@deepseek-ai/cordis' { // ---- Contract re-exports (browser-safe apiproxy channels + core types) ---- export type { ApiProxy, HostApi, - SkillsApi, SkillEntry, ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning, MessageId, ModelReasoningEffort, ModelSelection, RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode, ClientRequest, ServerResponse, RpcMessage, HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk, - SettingsApi, - ConfigurableProviderView, DiscoveredModelView, LlmApi, } from './api.ts' export { RpcId, diff --git a/packages/client/ui-agent-preset/src/client/index.ts b/packages/client/ui-agent-preset/src/client/index.ts index dda3c9d26e..4019655d72 100644 --- a/packages/client/ui-agent-preset/src/client/index.ts +++ b/packages/client/ui-agent-preset/src/client/index.ts @@ -65,7 +65,7 @@ export function apply(ctx: ClientContext): void { // One roster, four surfaces. The chip is registered in a later scope, so it // subscribes here rather than being reached from this one. const rosterReaders = new Set<() => void>() - const section = new AgentPresetSectionController({ ...api, ...settingsWire }, ctx.remote, () => { + const section = new AgentPresetSectionController(api, ctx.remote, () => { void controller.load() for (const read of rosterReaders) read() }) diff --git a/packages/client/ui-agent-preset/src/client/section-store.ts b/packages/client/ui-agent-preset/src/client/section-store.ts index 099d92b186..70baab32ec 100644 --- a/packages/client/ui-agent-preset/src/client/section-store.ts +++ b/packages/client/ui-agent-preset/src/client/section-store.ts @@ -15,7 +15,6 @@ */ import type { ClientRemote, IApiClient } from '@deepseek-ai/dsh-api-remotes/client' -import type { SettingsWireFace } from '@deepseek-ai/dsh-client-ui-settings/client' import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store' import { beginRosterRead, messageOf, writeDefaultPreset } from './settings-store.ts' @@ -134,8 +133,8 @@ export class AgentPresetSectionController { readonly store: SnapshotStore = createSnapshotStore(INITIAL) constructor( - private readonly api: SettingsWireFace & Pick, - private readonly remote: Pick, + private readonly api: Pick, + private readonly remote: Pick, /** * Called after this page changes the roster DIRECTORY, so the other * surfaces reading the same roster re-read it. A settings field moving is @@ -296,13 +295,13 @@ export class AgentPresetSectionController { */ async openLocation(id: string): Promise { try { - const response = await this.api.agentPresets.openDocument({ agentPreset: id }) - if (!response.result.ok) { - this.set({ error: response.result.error.message }) + const result = await this.remote.settings.openAgentPresetDirectory(id) + if (!result.ok) { + this.set({ error: result.error.message }) return } - if (response.result.value.opened) return - const { path } = response.result.value + if (result.value.opened) return + const { path } = result.value this.set({ revealedPaths: { ...this.store.getSnapshot().revealedPaths, [id]: path } }) } catch (error) { this.set({ error: messageOf(error) }) @@ -350,7 +349,7 @@ export class AgentPresetSectionController { * @returns once the write settled and the roster was re-read. */ async makeDefault(id: string): Promise { - const failure = await writeDefaultPreset(this.api, id) + const failure = await writeDefaultPreset(this.remote, id) if (failure !== undefined) { this.set({ error: failure }) return diff --git a/packages/client/ui-chat/package.json b/packages/client/ui-chat/package.json index 10382a63bf..fcafb63b8b 100644 --- a/packages/client/ui-chat/package.json +++ b/packages/client/ui-chat/package.json @@ -74,8 +74,7 @@ "@deepseek-ai/dsh-session-stats": "workspace:^", "@deepseek-ai/dsh-settings": "workspace:^", "@deepseek-ai/dsh-token-meter": "workspace:^", - "@deepseek-ai/dsh-tools": "workspace:^", - "@deepseek-ai/dsh-util-workspace-path": "workspace:^" + "@deepseek-ai/dsh-tools": "workspace:^" }, "devDependencies": { "@deepseek-ai/cordis": "workspace:^", @@ -105,7 +104,6 @@ "@deepseek-ai/dsh-settings": "workspace:^", "@deepseek-ai/dsh-token-meter": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", - "@deepseek-ai/dsh-util-workspace-path": "workspace:^", "@types/react": "~18.3.1", "react": "^18.2.0" }, diff --git a/packages/client/ui-chat/src/client/apply.ts b/packages/client/ui-chat/src/client/apply.ts index 1fc602e1ac..829b36bd0f 100644 --- a/packages/client/ui-chat/src/client/apply.ts +++ b/packages/client/ui-chat/src/client/apply.ts @@ -1,10 +1,10 @@ /** Register the Chat Conversation target, renderers, stats, and details surface. */ import type { Context } from '@deepseek-ai/cordis' import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment' +import type {} from '@deepseek-ai/dsh-api-remotes/client' import type { SessionBinding } from '@deepseek-ai/dsh-api-session-controller/client' import type { BoundActions, ObservableSnapshot } from '@deepseek-ai/dsh-client-store' import type { SessionId } from '@deepseek-ai/dsh-session/types' -import { resolveWorkspacePath } from '@deepseek-ai/dsh-util-workspace-path' // Type-only service and declaration merges used by the apply world. import type {} from '@deepseek-ai/dsh-client-locale/client' import type {} from '@deepseek-ai/dsh-client-ui-conversation/client' @@ -46,7 +46,8 @@ const CHAT_NODE_INJECT: ChatNodeTurnDataInjected = { /** Services required by the Chat target and its presentation registrations. */ export const inject = [ - 'slots', 'sessions', 'uiSession', 'uiConversation', 'uiWorkspace', 'layout', 'locale', 'settingsScope', + 'slots', 'sessions', 'uiSession', 'uiConversation', 'layout', 'locale', + 'settingsScope', 'remote', 'remote.session', ] /** @@ -115,9 +116,9 @@ export function apply(ctx: Context): void { ctx.layout.openDetails() }, fileMentions: (owner: TurnTailOwnerProps) => ctx.get('chatFileMentions')?.forClosing(owner), - openFile: (path) => { - const cwd = ctx.sessions.list.getSnapshot().byId[sessionId]?.cwd - return ctx.uiWorkspace.openPath(resolveWorkspacePath(cwd, path)) + openFile: async (path) => { + const result = await ctx.remote.session.openWorkspacePath({ sessionId, path }) + if (!result.ok) throw new Error(`path open failed: ${result.error.message}`) }, loadOlder: () => { void session.loadOlder() }, loadImage: Object.assign( diff --git a/packages/client/ui-chat/tsconfig.json b/packages/client/ui-chat/tsconfig.json index 4d42320885..0800260fbb 100644 --- a/packages/client/ui-chat/tsconfig.json +++ b/packages/client/ui-chat/tsconfig.json @@ -50,9 +50,6 @@ { "path": "../../runtime-diagnostics/invariants" }, - { - "path": "../../util/workspace-path" - }, { "path": "../../session/session-stats" }, diff --git a/packages/client/ui-model-selection/package.json b/packages/client/ui-model-selection/package.json index 0d54a69f01..d14dcd894d 100644 --- a/packages/client/ui-model-selection/package.json +++ b/packages/client/ui-model-selection/package.json @@ -33,7 +33,6 @@ "client": { "inject": [ "@deepseek-ai/dsh-api-session-controller", - "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-client-locale", "@deepseek-ai/dsh-client-ui-commands", "@deepseek-ai/dsh-api-remotes" @@ -49,7 +48,6 @@ "peerDependencies": { "@deepseek-ai/dsh-api-remotes": "workspace:^", "@deepseek-ai/dsh-api-session-controller": "workspace:^", - "@deepseek-ai/dsh-client-connection": "workspace:^", "@deepseek-ai/dsh-client-locale": "workspace:^", "@deepseek-ai/dsh-client-ui-commands": "workspace:^", "@deepseek-ai/dsh-client-ui-conversation": "workspace:^", @@ -64,7 +62,6 @@ "devDependencies": { "@deepseek-ai/dsh-api-remotes": "workspace:^", "@deepseek-ai/dsh-api-session-controller": "workspace:^", - "@deepseek-ai/dsh-client-connection": "workspace:^", "@deepseek-ai/dsh-client-locale": "workspace:^", "@deepseek-ai/dsh-client-test-runtime": "workspace:^", "@deepseek-ai/dsh-client-ui-commands": "workspace:^", diff --git a/packages/client/ui-model-selection/src/client/catalog.ts b/packages/client/ui-model-selection/src/client/catalog.ts index b0ec866a9a..5bfbcb2d1e 100644 --- a/packages/client/ui-model-selection/src/client/catalog.ts +++ b/packages/client/ui-model-selection/src/client/catalog.ts @@ -1,9 +1,6 @@ /** One Host-generation model catalog shared by every Session selector. */ -import { - type IApiClient, - type ModelCatalog, -} from '@deepseek-ai/dsh-client-connection/client' +import type { ClientRemote, ModelCatalog } from '@deepseek-ai/dsh-api-remotes/client' import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store' /** Observable lifecycle of the shared model catalog. */ @@ -25,8 +22,8 @@ export class ModelCatalogDirectory { private generation = 0 private inflight: Promise | undefined - /** @param api - shared connection API client. */ - constructor(private readonly api: IApiClient) {} + /** @param session - Session Remote namespace carrying the Host-generation catalog. */ + constructor(private readonly session: Pick) {} /** * Return the current generation's catalog, sharing its one in-flight load. @@ -41,14 +38,14 @@ export class ModelCatalogDirectory { draft.status = 'loading' draft.error = null }) - const operation = this.api.llm.models({}).then((response) => { - if (!response.result.ok) { - throw new Error(`${response.result.error.code}: ${response.result.error.message}`) + const operation = this.session.modelCatalog().then((response) => { + if (!response.ok) { + throw new Error(`${response.error.code}: ${response.error.message}`) } if (generation === this.generation) { - this.store.set({ value: response.result.value, status: 'ready', error: null }) + this.store.set({ value: response.value, status: 'ready', error: null }) } - return response.result.value + return response.value }).catch((error: unknown) => { if (generation === this.generation) { this.store.update((draft) => { diff --git a/packages/client/ui-model-selection/src/client/index.ts b/packages/client/ui-model-selection/src/client/index.ts index d68e0463d3..dd7d4a89fd 100644 --- a/packages/client/ui-model-selection/src/client/index.ts +++ b/packages/client/ui-model-selection/src/client/index.ts @@ -2,7 +2,7 @@ * Model selection plugin, browser half — TWO entries over ONE per-session * directory owned by ModelDirectoryResolver (`ctx.modelDirectories`). The /model popupSelect * contribution and the composer's named `conversation.input.model` seat share - * one Host-generation `llm.models` catalog, combine it with the Session's + * one Host-generation `session/modelCatalog` catalog, combine it with the Session's * durable model-selection projection, and submit through `session.selectModel`. * A switch made in either entry is what the other shows next. Failures * ride each entry's own retry surface (popup shell error/retry; seat menu diff --git a/packages/client/ui-model-selection/src/client/service.ts b/packages/client/ui-model-selection/src/client/service.ts index 7e01ecd5a2..2053d70b85 100644 --- a/packages/client/ui-model-selection/src/client/service.ts +++ b/packages/client/ui-model-selection/src/client/service.ts @@ -15,7 +15,6 @@ import { Service } from '@deepseek-ai/cordis' import type { Context } from '@deepseek-ai/cordis' import type {} from '@deepseek-ai/dsh-api-session-controller/client' -import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client' import type { SessionId } from '@deepseek-ai/dsh-session/types' import { ModelCatalogDirectory } from './catalog.ts' import { ModelDirectory } from './directory.ts' @@ -34,7 +33,7 @@ interface LiveState { /** The `ctx.modelDirectories` session model-selection service. */ export class ModelDirectoryResolver extends Service { - static inject = ['sessions', 'remote', 'remote.session', 'connection'] + static inject = ['sessions', 'remote', 'remote.session'] private readonly live: LiveState = { directories: new Map() } private readonly catalog: ModelCatalogDirectory @@ -49,9 +48,7 @@ export class ModelDirectoryResolver extends Service { constructor(ctx: Context, config: { blockReason: () => string }) { super(ctx, 'modelDirectories') this.blockReason = config.blockReason - const connection = ctx.get('connection') as ConnectionHandle | undefined - if (connection === undefined) throw new Error('ui-model-selection: connection service is unavailable') - this.catalog = new ModelCatalogDirectory(connection.api) + this.catalog = new ModelCatalogDirectory(ctx.remote.session) void this.catalog.load().catch(() => { /* selectors expose the shared error */ }) ctx.on('connection/reset', () => { this.catalog.resetGeneration() diff --git a/packages/client/ui-settings-general/src/client/index.ts b/packages/client/ui-settings-general/src/client/index.ts index d1342e9b1d..abdf2829b3 100644 --- a/packages/client/ui-settings-general/src/client/index.ts +++ b/packages/client/ui-settings-general/src/client/index.ts @@ -55,7 +55,7 @@ const NS = 'settings' * ui-settings' apply, whose activation order relative to this one is NOT * constrained; registrations depend on their slots through `slots.inject()`. */ -export const inject = ['slots', 'locale', 'connection', 'settingsScope'] +export const inject = ['slots', 'locale', 'connection', 'remote', 'remote.settings', 'settingsScope'] /** * Register the `settings` dictionaries, the chrome content, and the General @@ -72,7 +72,7 @@ export function apply(ctx: ClientContext): void { const connection = ctx.get('connection') as ConnectionHandle // The shared SettingsScope mirror updates after document commits and reconnects. const documentController = connection.isLoopback - ? new SettingsDocumentStore(connection.api, ctx.settingsScope.describe()) + ? new SettingsDocumentStore(ctx.remote, ctx.settingsScope.describe()) : undefined const documentInjected = documentController === undefined ? undefined diff --git a/packages/client/ui-settings-general/src/client/settings-document-store.ts b/packages/client/ui-settings-general/src/client/settings-document-store.ts index b545ec66f3..4e7fec2ecd 100644 --- a/packages/client/ui-settings-general/src/client/settings-document-store.ts +++ b/packages/client/ui-settings-general/src/client/settings-document-store.ts @@ -1,6 +1,6 @@ /** State owner for the optional local settings-document action. */ -import type { IApiClient } from '@deepseek-ai/dsh-api-remotes/client' +import type { ClientRemote } from '@deepseek-ai/dsh-api-remotes/client' import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store' import type { SettingsDescribeFace } from '@deepseek-ai/dsh-client-ui-settings/client' @@ -32,7 +32,7 @@ export class SettingsDocumentStore { * @param describeFace - the shared mirror's describe face (`hasDocument` source). */ constructor( - private readonly api: Pick, + private readonly remote: Pick, private readonly describeFace: SettingsDescribeFace, ) {} @@ -63,8 +63,8 @@ export class SettingsDocumentStore { state.error = null }) try { - const response = await this.api.settings.openDocument({}) - if (!response.result.ok) throw new Error(response.result.error.message) + const result = await this.remote.settings.openSettingsDocument() + if (!result.ok) throw new Error(result.error.message) } catch (error) { this.store.update((state) => { state.error = messageOf(error) }) } finally { diff --git a/packages/client/ui-settings-models/package.json b/packages/client/ui-settings-models/package.json index 5366374f39..57fa2abcc6 100644 --- a/packages/client/ui-settings-models/package.json +++ b/packages/client/ui-settings-models/package.json @@ -47,7 +47,6 @@ "peerDependencies": { "@deepseek-ai/cordis": "workspace:^", "@deepseek-ai/dsh-api-remotes": "workspace:^", - "@deepseek-ai/dsh-client-connection": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-client-locale": "workspace:^", "@deepseek-ai/dsh-client-ui-settings": "workspace:^", @@ -55,7 +54,6 @@ }, "devDependencies": { "@deepseek-ai/dsh-api-remotes": "workspace:^", - "@deepseek-ai/dsh-client-connection": "workspace:^", "@deepseek-ai/dsh-client-locale": "workspace:^", "@deepseek-ai/dsh-client-store": "workspace:^", "@deepseek-ai/dsh-client-test-runtime": "workspace:^", diff --git a/packages/client/ui-settings-models/src/client/ModelListEditor.tsx b/packages/client/ui-settings-models/src/client/ModelListEditor.tsx index 20276412bb..a1be9f9085 100644 --- a/packages/client/ui-settings-models/src/client/ModelListEditor.tsx +++ b/packages/client/ui-settings-models/src/client/ModelListEditor.tsx @@ -16,11 +16,11 @@ import { useState } from 'react' import type { ReactNode } from 'react' -import type { DiscoveredModelView, IApiClient } from '@deepseek-ai/dsh-api-remotes/client' +import type { LlmDiscoveredModel } from '@deepseek-ai/dsh-api-remotes/client' import { Button, Modal } from '@deepseek-ai/dsh-client-ui-primitives' import { formatCapacity, parseCapacity } from './DeepSeekModelsEditor.tsx' import type { DeepSeekModelDraft } from './DeepSeekModelsEditor.tsx' -import { messageOf } from './store.ts' +import { messageOf, type ModelsWire } from './store.ts' import type { en } from './locales.ts' import styles from './ModelsSection.module.css' @@ -80,7 +80,7 @@ export interface ModelListEditorProps { */ probeBlocked?: keyof typeof en | undefined /** Wire face the fetch action calls. */ - api: Pick + api: Pick /** Section copy. */ t: (key: keyof typeof en) => string /** Disable every control (read-only deployment or a pending write). */ @@ -142,7 +142,7 @@ function capacitySpelling(value: number | undefined): string { } /** Adopt a candidate, keeping whatever capacities the provider disclosed. */ -function adopt(candidate: DiscoveredModelView): ModelDraft { +function adopt(candidate: LlmDiscoveredModel): ModelDraft { return { id: candidate.id, ...candidate.name === undefined ? {} : { name: candidate.name }, @@ -160,7 +160,7 @@ export function ModelListEditor(props: ModelListEditorProps): ReactNode { const { models, onChange, probe, api, t, disabled } = props const [busy, setBusy] = useState(false) const [failure, setFailure] = useState(undefined) - const [candidates, setCandidates] = useState(undefined) + const [candidates, setCandidates] = useState(undefined) const [picked, setPicked] = useState>(new Set()) // Rows carry an id and a name; capacities are the exception, so they stay // folded until asked for rather than crowding every row with four inputs. @@ -229,18 +229,17 @@ export function ModelListEditor(props: ModelListEditorProps): ReactNode { setBusy(true) setFailure(undefined) try { - const response = await api.llm.discoverModels({ - settingsNs: probe.settingsNs, + const response = await api.llm.discoverModels(probe.settingsNs, { ...probe.provider === undefined ? {} : { provider: probe.provider }, ...probe.baseURL === undefined || probe.baseURL.length === 0 ? {} : { baseURL: probe.baseURL }, ...probe.api === undefined ? {} : { api: probe.api }, ...probe.apiKey === undefined ? {} : { apiKey: probe.apiKey }, }) - if (!response.result.ok) { - setFailure(response.result.error.message) + if (!response.ok) { + setFailure(response.error.message) return } - const found = response.result.value.models + const found = response.value if (found.length === 0) { setFailure(t('fetchEmpty')) return diff --git a/packages/client/ui-settings-models/src/client/index.ts b/packages/client/ui-settings-models/src/client/index.ts index b975c91f1f..c397d795c9 100644 --- a/packages/client/ui-settings-models/src/client/index.ts +++ b/packages/client/ui-settings-models/src/client/index.ts @@ -7,7 +7,6 @@ * packages/client/AGENTS.md. */ import type { Context as ClientContext } from '@deepseek-ai/cordis' -import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client' // Type-only: pulls the shell's SlotMap merge (the 'settings.section' entry). import type {} from '@deepseek-ai/dsh-client-ui-settings/client' // Type-only: pulls the locale plugin's Context merge (ctx.locale). @@ -42,7 +41,9 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { /** Dictionary namespace owned by this plugin. */ const NS = 'settings.models' -export type { ModelsCredentials, ModelsSettingsState, ModelsWire, ProviderRow } from './store.ts' +export type { + ModelsCredentials, ModelsLlm, ModelsSettingsState, ModelsWire, ProviderDirectoryEntry, ProviderRow, +} from './store.ts' /** * Refetch the page snapshot only after its first load: an unopened Models @@ -60,7 +61,7 @@ export function refreshIfLoaded(controller: ModelsSettingsStore): void { * constrained; registration depends on each slot through `slots.inject()`. */ export const inject = [ - 'slots', 'locale', 'connection', 'remote', 'remote.credentials', 'remote.settings', + 'slots', 'locale', 'remote', 'remote.credentials', 'remote.llm', 'remote.settings', 'settingsScope', 'settingsSchema', ] @@ -73,14 +74,11 @@ export const inject = [ export function apply(ctx: ClientContext): void { ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-models: copy dictionaries') - const connection = ctx.get('connection') as ConnectionHandle const schema = createSettingsSchemaOperations(ctx.settingsSchema) - // The page's two carriers under one face: model discovery and the catalog - // still ride the unary API, while settings and credentials are Remote - // namespaces. + // Every configuration operation rides its owning Remote namespace. const wire: ModelsWire = { - ...connection.api, credentials: ctx.remote.credentials, + llm: ctx.remote.llm, settings: ctx.remote.settings, } const controller = new ModelsSettingsStore(wire, schema, ctx.settingsScope.describe()) diff --git a/packages/client/ui-settings-models/src/client/slot-contract.ts b/packages/client/ui-settings-models/src/client/slot-contract.ts index b8360e21c9..a9eef19368 100644 --- a/packages/client/ui-settings-models/src/client/slot-contract.ts +++ b/packages/client/ui-settings-models/src/client/slot-contract.ts @@ -4,7 +4,7 @@ * without editing it. * * `settings.models.provider-card` is keyed by the row's owning settings - * namespace (`ConfigurableProviderView.settingsNs`): an adapter family's + * namespace (`ProviderDirectoryEntry.settingsNs`): an adapter family's * companion plugin registers one entry under the family's namespace and * receives every card of that family — shipped, added, and hand-declared rows * alike — while the section never learns what the namespace means. Keying on @@ -17,8 +17,8 @@ * the declaration. The types therefore live with their declarer. */ -import type { ConfigurableProviderView } from '@deepseek-ai/dsh-api-remotes/client' import type {} from '@deepseek-ai/dsh-client-ui-slots' +import type { ProviderDirectoryEntry } from './store.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface SlotMap { @@ -42,7 +42,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { /** Owner share of one provider-card extension occurrence. */ export interface ProviderCardExtrasOwnerProps { /** The card's directory row (route id, display name, settings address, live state). */ - provider: ConfigurableProviderView + provider: ProviderDirectoryEntry /** Whether any layer configures this provider (its profile resolves); `false` while the add-provider draft edits a dormant row. */ configured: boolean /** Whether the row's referenced api-key credential is confirmed configured (the page's credential join). */ diff --git a/packages/client/ui-settings-models/src/client/store.ts b/packages/client/ui-settings-models/src/client/store.ts index 6f664bd068..fe98870321 100644 --- a/packages/client/ui-settings-models/src/client/store.ts +++ b/packages/client/ui-settings-models/src/client/store.ts @@ -1,13 +1,14 @@ /** * Models settings page store: one snapshot joining the configurable-provider - * directory (`llm.providers`), the settings namespaces (shared settings mirror), + * directory (`llm/listProviders` joined with `llm/listConfigurableProviders`), + * the settings namespaces (shared settings mirror), * and the referenced credentials (`credentials/describe`). The host stays the * single fact source — every mutation writes through the wire and the page * re-renders from the next describe, pushed or refetched. */ import type { - ClientRemote, ConfigurableProviderView, CredentialInfo, IApiClient, SettingsNamespaceView, + ClientRemote, CredentialInfo, LlmConfigurableProvider, LlmProviderInfo, SettingsNamespaceView, } from '@deepseek-ai/dsh-api-remotes/client' import type { SnapshotStore } from '@deepseek-ai/dsh-client-store' import { createSnapshotStore } from '@deepseek-ai/dsh-client-store' @@ -23,22 +24,71 @@ const PROBE_ROUTE = '\u0000probe' /** The credentials Remote methods the Models page reads and writes through. */ export type ModelsCredentials = Pick +/** LLM Remote methods used by the Models page. */ +export type ModelsLlm = Pick< + ClientRemote['llm'], + 'discoverModels' | 'listConfigurableProviders' | 'listProviders' +> + +/** One provider row after joining the configurable directory with live routes. */ +export interface ProviderDirectoryEntry { + readonly provider: string + readonly displayName: string + readonly settingsNs: string + readonly settingsPath: readonly string[] + readonly active: boolean + readonly declared?: boolean +} + /** - * Every wire face the Models page reaches: the settings and llm unary domains, - * plus the credentials Remote namespace, which is addressed by reference name - * and never answers with a value. + * Join declared configurable providers with the currently registered routes. + * @param registered - live provider routes in registration order. + * @param directory - declared configurable providers in declaration order. + * @returns declared rows followed by live routes with no declaration. */ -export interface ModelsWire extends Pick { +export function joinProviderDirectory( + registered: readonly LlmProviderInfo[], + directory: readonly LlmConfigurableProvider[], +): ProviderDirectoryEntry[] { + const active = new Set(registered.map(provider => provider.id)) + const declared = new Set(directory.map(entry => entry.provider)) + const rows: ProviderDirectoryEntry[] = directory.map(entry => ({ + provider: entry.provider, + displayName: entry.displayName, + settingsNs: entry.settingsNs, + settingsPath: [...entry.settingsPath], + active: active.has(entry.provider), + ...entry.declared === undefined ? {} : { declared: entry.declared }, + })) + for (const provider of registered) { + if (declared.has(provider.id)) continue + rows.push({ + provider: provider.id, + displayName: provider.name, + settingsNs: '', + settingsPath: [], + active: true, + }) + } + return rows +} + +/** + * Every Remote wire face the Models page reaches. + */ +export interface ModelsWire { /** The settings Remote namespace: the redacted read and the profile writes. */ settings: SettingsRemote /** Credential state and writes for the references provider profiles name. */ credentials: ModelsCredentials + /** Provider directory reads and draft endpoint discovery. */ + llm: ModelsLlm } /** One provider row the page renders. */ export interface ProviderRow { /** The directory entry (route id, display name, settings address, live state). */ - entry: ConfigurableProviderView + entry: ProviderDirectoryEntry /** Whether any layer configures this provider (its profile resolves). */ configured: boolean /** Whether the user layer alone carries the profile (removal restores the base). */ @@ -157,20 +207,22 @@ export class ModelsSettingsStore { async load(): Promise { const generation = ++this.generation this.store.update((s) => { s.status = 'loading'; s.error = null }) - let providers: ConfigurableProviderView[] + let providers: ProviderDirectoryEntry[] let writable: boolean let views: readonly SettingsNamespaceView[] try { - const [providersResponse] = await Promise.all([ - this.api.llm.providers({}), + const [registered, declared] = await Promise.all([ + this.api.llm.listProviders(), + this.api.llm.listConfigurableProviders(), this.describeFace.ensure(), ]) - if (!providersResponse.result.ok) throw new Error(providersResponse.result.error.message) + if (!registered.ok) throw new Error(registered.error.message) + if (!declared.ok) throw new Error(declared.error.message) const mirrored = this.describeFace.getSnapshot() if (mirrored.view === undefined) { throw new Error(mirrored.error ?? 'settings are unavailable in this browser') } - providers = providersResponse.result.value.providers + providers = joinProviderDirectory(registered.value, declared.value) writable = mirrored.view.writable views = mirrored.view.namespaces } catch (error) { diff --git a/packages/client/ui-settings-plugins/src/client/index.ts b/packages/client/ui-settings-plugins/src/client/index.ts index 8dd09f1d03..40ec376432 100644 --- a/packages/client/ui-settings-plugins/src/client/index.ts +++ b/packages/client/ui-settings-plugins/src/client/index.ts @@ -9,7 +9,6 @@ * settings scope, which keeps them unaware of one another and of other tabs. */ -import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client' // Type-only: pulls the locale plugin's Context merge (ctx.locale). import type {} from '@deepseek-ai/dsh-client-locale/client' // Type-only: the settings shell's SlotMap merge (the 'settings.section' entry) @@ -54,14 +53,15 @@ export type { WebSearchCardFace, WebSearchCardState } from './web-search-card-co const NS = 'settings.plugins' /** Required services (cordis fiber inject). */ -export const inject = ['slots', 'locale', 'connection', 'remote', 'remote.credentials', 'settingsScope'] +export const inject = [ + 'slots', 'locale', 'connection', 'remote', 'remote.credentials', 'remote.session', 'settingsScope', +] /** * Mount the plugin configuration section and the cards this package ships. * @param ctx - the browser plugin context. */ export function apply(ctx: ClientContext): void { - const { api } = ctx.get('connection') as ConnectionHandle const t = ctx.locale.bind(NS) ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-plugins: section dictionaries') @@ -71,7 +71,7 @@ export function apply(ctx: ClientContext): void { ctx.settingsScope.bind({ namespace: WEB_SEARCH_NS }), ctx.remote.credentials) const subagentModelSelection = new SubagentModelSelectionCardController( ctx.settingsScope.bind({ namespace: SUBAGENT_MODEL_SELECTION_NS }), - api, + ctx.remote.session, ) // The credential a card reports is not part of any settings section, so its diff --git a/packages/client/ui-settings-plugins/src/client/subagent-model-selection-card-controller.ts b/packages/client/ui-settings-plugins/src/client/subagent-model-selection-card-controller.ts index 053074a1ff..9e1b5c2d2a 100644 --- a/packages/client/ui-settings-plugins/src/client/subagent-model-selection-card-controller.ts +++ b/packages/client/ui-settings-plugins/src/client/subagent-model-selection-card-controller.ts @@ -1,7 +1,7 @@ /** Staged editor for the Host-owned subagent model allowlist. */ import type { - IApiClient, + ClientRemote, ModelProviderGroup, } from '@deepseek-ai/dsh-api-remotes/client' import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store' @@ -145,11 +145,11 @@ export class SubagentModelSelectionCardController { /** * @param scope - bound `subagent-model-selection` settings scope. - * @param api - Host LLM directory face. + * @param session - Host Session model-catalog face. */ constructor( private readonly scope: SettingsScope, - private readonly api: Pick, + private readonly session: Pick, ) { this.store = createSnapshotStore(this.projection()) this.unsubscribe = scope.subscribe(() => { @@ -321,11 +321,11 @@ export class SubagentModelSelectionCardController { this.catalogPartial = false this.publish() try { - const response = await this.api.llm.models({}) + const response = await this.session.modelCatalog() if (generation !== this.catalogGeneration) return - if (!response.result.ok) throw new Error(response.result.error.message) - this.catalogGroups = response.result.value.groups - this.catalogPartial = response.result.value.failures.length > 0 + if (!response.ok) throw new Error(response.error.message) + this.catalogGroups = response.value.groups + this.catalogPartial = response.value.failures.length > 0 this.catalogStatus = 'ready' } catch { if (generation !== this.catalogGeneration) return diff --git a/packages/client/ui-skill/src/client/index.ts b/packages/client/ui-skill/src/client/index.ts index 9b66956c2d..5b6a9bb44e 100644 --- a/packages/client/ui-skill/src/client/index.ts +++ b/packages/client/ui-skill/src/client/index.ts @@ -1,6 +1,6 @@ /** * Skill reference plugin, browser half: registers the '/' skill source — - * candidates from the skill.list RPC addressed by the per-call session + * candidates from the `skills/list` Remote addressed by the per-call session * projection's sessionId (sessions are always agent-backed; the host * resolves cwd from the session header). A pick lands the literal `/name ` * text and the prompt ships the same literal (plain-text-reference decision; @@ -10,7 +10,7 @@ * leading `/name` naming a user-invocable skill and injects the rendered * body for every entry point, including `disable-model-invocation` skills the * model-side catalog never lists (issue #1470). The RPC rides the plugin's - * root-context connection captured at registration — the source never reads + * root-context Remote captured at registration — the source never reads * services off a per-call argument. Draft chip visuals derive from * the lexicon scan; this source implements no reference codec. * @@ -31,7 +31,7 @@ */ // Type-only: the carrier types, the forwarded Host-event face and the ctx.remote merge. import type { Context as ClientContext } from '@deepseek-ai/cordis' -import type { ConnectionHandle, SkillEntry } from '@deepseek-ai/dsh-api-remotes/client' +import type { SkillEntry } from '@deepseek-ai/dsh-api-remotes/client' import type {} from '@deepseek-ai/dsh-api-session-controller/client' import type { SessionId } from '@deepseek-ai/dsh-session/types' import type { InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client' @@ -71,7 +71,7 @@ export function apply(ctx: ClientContext): void { SkillRow, )) - const skills = (ctx.get('connection') as ConnectionHandle).api.skills + const skills = ctx.remote.skills const sessions = ctx.sessions // Session-keyed catalog cache; single-flight per key. Plugin-closure state: // the fiber effect below is its teardown boundary. @@ -98,8 +98,8 @@ export function apply(ctx: ClientContext): void { if (existing !== undefined) return existing.promise const abort = new AbortController() const promise = (async () => { - const { result } = await skills.list({ sessionId }, abort.signal) - if (!result.ok) throw new Error(`skill.list failed: ${result.error.code}: ${result.error.message}`) + const result = await skills.list({ sessionId }, abort.signal) + if (!result.ok) throw new Error(`skills/list failed: ${result.error.code}: ${result.error.message}`) return result.value.skills })() const entry: CatalogFetch = { promise, abort } diff --git a/packages/client/ui-workspace/src/client/index.ts b/packages/client/ui-workspace/src/client/index.ts index 7f87202365..41a9b80fd1 100644 --- a/packages/client/ui-workspace/src/client/index.ts +++ b/packages/client/ui-workspace/src/client/index.ts @@ -75,7 +75,7 @@ export function apply(ctx: Context): void { const workspaces = ctx.get('workspaces') as IWorkspaces const hostDescription = connection.hostDescription const uiWorkspace = new UiWorkspaceService( - ctx, connection.api, ctx.remote.directoryPicker, workspaces, sessions) + ctx, ctx.remote.directoryPicker, workspaces, sessions) ctx.slots.provideRoot({ hooks: { workspaces: workspaces.list } }) ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-workspace: dictionaries') diff --git a/packages/client/ui-workspace/src/client/navigation.ts b/packages/client/ui-workspace/src/client/navigation.ts index 4d0f3e3fa9..a4e3eb3880 100644 --- a/packages/client/ui-workspace/src/client/navigation.ts +++ b/packages/client/ui-workspace/src/client/navigation.ts @@ -1,7 +1,6 @@ /** Workspace archive and directory UI capability. */ import { Service, type Context } from '@deepseek-ai/cordis' -import type { IApiClient } from '@deepseek-ai/dsh-client-connection/client' import type { ClientRemote, DirectoryListing } from '@deepseek-ai/dsh-api-remotes/client' import type { RemoteFailure } from '@deepseek-ai/dsh-typert-protocol' import type { @@ -50,11 +49,6 @@ export interface UiWorkspace { * @returns created absolute path. */ createDirectory(path: string, name: string): Promise - /** - * Open a path with the Host operating system. - * @param path - absolute or Host-resolvable path. - */ - openPath(path: string): Promise } declare module '@deepseek-ai/cordis' { @@ -80,14 +74,12 @@ class UiWorkspaceService extends Service implements UiWorkspace { /** * @param ctx - Client root Context. - * @param api - shared Host API carrier. * @param directoryPicker - the directory-picking Remote namespace. * @param workspaces - pure Workspace Controller. * @param sessions - pure Session Controller. */ constructor( ctx: Context, - private readonly api: IApiClient, private readonly directoryPicker: ClientRemote['directoryPicker'], private readonly workspaces: IWorkspaces, private readonly sessions: ISessions, @@ -163,13 +155,6 @@ class UiWorkspaceService extends Service implements UiWorkspace { return result.value } - async openPath(path: string): Promise { - const response = await this.api.host.openPath({ path }) - if (!response.result.ok) { - throw new Error(`path open failed: ${response.result.error.message}`) - } - } - private watchNavigation(): () => void { let initial: 'waiting' | 'connecting' | 'done' = 'waiting' let disposed = false