mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
refactor(client): consume migrated Remote namespaces
This commit is contained in:
@@ -5,8 +5,8 @@ import agentPresetsRemote from '@deepseek-ai/dsh-agent-presets/remote'
|
|||||||
import commandsRemote from '@deepseek-ai/dsh-commands/remote'
|
import commandsRemote from '@deepseek-ai/dsh-commands/remote'
|
||||||
import settingsControllerRemote from '@deepseek-ai/dsh-api-settings-controller/remote'
|
import settingsControllerRemote from '@deepseek-ai/dsh-api-settings-controller/remote'
|
||||||
import goalsRemote from '@deepseek-ai/dsh-goal/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 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 pluginInventoryRemote from '@deepseek-ai/dsh-host-plugin-inventory/remote'
|
||||||
import messageFeedbackRemote from '@deepseek-ai/dsh-message-feedback/remote'
|
import messageFeedbackRemote from '@deepseek-ai/dsh-message-feedback/remote'
|
||||||
import sessionReferencesRemote from '@deepseek-ai/dsh-session-reference/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-agent-presets/remote'
|
||||||
export type {} from '@deepseek-ai/dsh-commands/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-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-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-host-plugin-inventory/remote'
|
||||||
export type {} from '@deepseek-ai/dsh-message-feedback/remote'
|
export type {} from '@deepseek-ai/dsh-message-feedback/remote'
|
||||||
export type {} from '@deepseek-ai/dsh-session-reference/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.
|
* the carrier's runtime values stay behind their own module edge.
|
||||||
*/
|
*/
|
||||||
export type {
|
export type {
|
||||||
ConfigurableProviderView, ConnectionHandle, ConnectionSinks, ContentBlock,
|
ConnectionHandle, ConnectionSinks, ContentBlock, IApiClient,
|
||||||
DiscoveredModelView, IApiClient,
|
MessageId, ModelCatalogFailure, ModelProviderGroup, ModelReasoningEffort, ModelSelection,
|
||||||
MessageId, ModelCatalog, ModelCatalogFailure, ModelProviderGroup, ModelReasoningEffort, ModelSelection,
|
|
||||||
RpcError, RpcId, RpcRequest, RpcResponse, RpcResult, SessionId,
|
RpcError, RpcId, RpcRequest, RpcResponse, RpcResult, SessionId,
|
||||||
SkillEntry, StreamChunk,
|
StreamChunk,
|
||||||
} from '@deepseek-ai/dsh-client-connection/client'
|
} from '@deepseek-ai/dsh-client-connection/client'
|
||||||
export type {} from '@deepseek-ai/dsh-api-gateway/client'
|
export type {} from '@deepseek-ai/dsh-api-gateway/client'
|
||||||
export type {} from '@deepseek-ai/dsh-cordis-host-runner/remote'
|
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 {
|
export type {
|
||||||
SettingsDescribeValue, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView,
|
SettingsDescribeValue, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView,
|
||||||
} from '@deepseek-ai/dsh-settings/types'
|
} 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
|
// Reference-discovery result vocabulary for the fileReferences and
|
||||||
// sessionReferenceResolver namespaces.
|
// sessionReferenceResolver namespaces.
|
||||||
export type { FileReferenceCandidate } from '@deepseek-ai/dsh-file-reference/types'
|
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-session-controller/types').SessionError
|
||||||
| import('@deepseek-ai/dsh-api-settings-controller/types').CredentialError
|
| import('@deepseek-ai/dsh-api-settings-controller/types').CredentialError
|
||||||
| import('@deepseek-ai/dsh-api-settings-controller/types').SettingsError
|
| 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-subagent/client').SubagentControlError
|
||||||
| import('@deepseek-ai/dsh-api-workspace-controller/types').WorkspaceError
|
| import('@deepseek-ai/dsh-api-workspace-controller/types').WorkspaceError
|
||||||
|
|
||||||
@@ -150,8 +155,7 @@ export async function apply(ctx: Context): Promise<() => Promise<void>> {
|
|||||||
const disposers: Array<() => Promise<void>> = []
|
const disposers: Array<() => Promise<void>> = []
|
||||||
try {
|
try {
|
||||||
for (const contribution of [
|
for (const contribution of [
|
||||||
agentPresetsRemote, commandsRemote, settingsControllerRemote, goalsRemote, dynamicRemote,
|
agentPresetsRemote, commandsRemote, settingsControllerRemote, goalsRemote, llmRemote, dynamicRemote,
|
||||||
fileReferencesRemote,
|
|
||||||
pluginInventoryRemote, messageFeedbackRemote, sessionReferencesRemote,
|
pluginInventoryRemote, messageFeedbackRemote, sessionReferencesRemote,
|
||||||
subagentsRemote, sessionRemote, workspaceRemote,
|
subagentsRemote, sessionRemote, workspaceRemote,
|
||||||
]) {
|
]) {
|
||||||
|
|||||||
@@ -8,11 +8,8 @@
|
|||||||
export type {
|
export type {
|
||||||
ApiProxy, HostApi,
|
ApiProxy, HostApi,
|
||||||
ResponseValue,
|
ResponseValue,
|
||||||
SkillsApi, SkillEntry,
|
|
||||||
ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
|
ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
|
||||||
ModelReasoningEffort, ModelSelection,
|
ModelReasoningEffort, ModelSelection,
|
||||||
SettingsApi,
|
|
||||||
ConfigurableProviderView, DiscoveredModelView, LlmApi,
|
|
||||||
} from '@deepseek-ai/dsh-host-apiproxy/api'
|
} from '@deepseek-ai/dsh-host-apiproxy/api'
|
||||||
export type {
|
export type {
|
||||||
RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
|
RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ const OPENAI_REASONING = {
|
|||||||
defaultEffort: 'medium',
|
defaultEffort: 'medium',
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Catalog served by `llm.models` (fresh copies per call). */
|
/** Catalog served by `session/modelCatalog` (fresh copies per call). */
|
||||||
function fixtureModelGroups(): ModelProviderGroup[] {
|
function fixtureModelGroups(): ModelProviderGroup[] {
|
||||||
return [
|
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 = {
|
const credentialRemotes = {
|
||||||
@@ -1999,10 +2018,6 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
|
|||||||
function ok<P, T>(request: RpcRequest<P>, value: T): Promise<RpcResponse<T>> {
|
function ok<P, T>(request: RpcRequest<P>, value: T): Promise<RpcResponse<T>> {
|
||||||
return Promise.resolve({ rpcId: request.rpcId, result: { ok: true, value } })
|
return Promise.resolve({ rpcId: request.rpcId, result: { ok: true, value } })
|
||||||
}
|
}
|
||||||
function err<P, T>(request: RpcRequest<P>, error: Extract<RpcResult<T>, { ok: false }>['error']): Promise<RpcResponse<T>> {
|
|
||||||
return Promise.resolve({ rpcId: request.rpcId, result: { ok: false, error } })
|
|
||||||
}
|
|
||||||
|
|
||||||
function sessionOk<T>(value: T): Promise<ConnectionRpcResult<T>> {
|
function sessionOk<T>(value: T): Promise<ConnectionRpcResult<T>> {
|
||||||
return Promise.resolve({ ok: true, value })
|
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)
|
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<RpcResponse<never>> | 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 = (
|
const requireRemoteSession = (
|
||||||
request: { readonly sessionId: SessionId },
|
request: { readonly sessionId: SessionId },
|
||||||
): Promise<ConnectionRpcResult<never>> | undefined => {
|
): Promise<ConnectionRpcResult<never>> | undefined => {
|
||||||
@@ -3398,65 +3403,6 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
|
|||||||
describe: request => ok(request, {
|
describe: request => ok(request, {
|
||||||
version: '0.0.0-fixture', cwd: '/tmp/fixture', attachedSessions, home: FIXTURE_HOME, canOpenPath: true,
|
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
|
// Satisfies the ApiProxy contract type only: the browser export button
|
||||||
// hands GET /api/session.export to the native download manager, so this
|
// hands GET /api/session.export to the native download manager, so this
|
||||||
@@ -3484,6 +3430,7 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
|
|||||||
refs?: readonly string[]
|
refs?: readonly string[]
|
||||||
value?: string
|
value?: string
|
||||||
ns?: string
|
ns?: string
|
||||||
|
settingsNs?: string
|
||||||
agentPreset?: string
|
agentPreset?: string
|
||||||
from?: string
|
from?: string
|
||||||
id?: 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/set': return Promise.resolve(credentialRemotes.set(args.ref as string))
|
||||||
case 'credentials/unset': return Promise.resolve(credentialRemotes.unset(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/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/update': return Promise.resolve(settingsRemotes.update(args.ns as string))
|
||||||
case 'settings/replace': return Promise.resolve(settingsRemotes.replace(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))
|
case 'settings/mutate': return Promise.resolve(settingsRemotes.mutate(args.ns as string))
|
||||||
@@ -3643,13 +3642,13 @@ export class FixtureApiClient extends AbstractApiClient {
|
|||||||
payload: RequestPayload<K>,
|
payload: RequestPayload<K>,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<RpcResponse<ResponseValue<K>>> {
|
): Promise<RpcResponse<ResponseValue<K>>> {
|
||||||
|
void signal
|
||||||
const request = rpcRequest(payload)
|
const request = rpcRequest(payload)
|
||||||
const full: ClientRequest = { type: 'client-request', rpcId: request.rpcId, method, payload }
|
const full: ClientRequest = { type: 'client-request', rpcId: request.rpcId, method, payload }
|
||||||
this.onEnvelope(full)
|
this.onEnvelope(full)
|
||||||
const response = await this.dispatch(
|
const response = await this.dispatch(
|
||||||
method,
|
method,
|
||||||
request as RpcRequest<never>,
|
request as RpcRequest<never>,
|
||||||
signal ?? new AbortController().signal,
|
|
||||||
) as RpcResponse<ResponseValue<K>>
|
) as RpcResponse<ResponseValue<K>>
|
||||||
const fullResponse: ServerResponse = { type: 'server-response', rpcId: response.rpcId, result: response.result }
|
const fullResponse: ServerResponse = { type: 'server-response', rpcId: response.rpcId, result: response.result }
|
||||||
this.onEnvelope(fullResponse)
|
this.onEnvelope(fullResponse)
|
||||||
@@ -3660,17 +3659,9 @@ export class FixtureApiClient extends AbstractApiClient {
|
|||||||
private dispatch(
|
private dispatch(
|
||||||
method: keyof RpcMethodMap,
|
method: keyof RpcMethodMap,
|
||||||
request: RpcRequest<never>,
|
request: RpcRequest<never>,
|
||||||
signal: AbortSignal,
|
|
||||||
): Promise<RpcResponse<unknown>> {
|
): Promise<RpcResponse<unknown>> {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'host.describe': return this.api.host.describe(request)
|
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,14 +31,11 @@ declare module '@deepseek-ai/cordis' {
|
|||||||
// ---- Contract re-exports (browser-safe apiproxy channels + core types) ----
|
// ---- Contract re-exports (browser-safe apiproxy channels + core types) ----
|
||||||
export type {
|
export type {
|
||||||
ApiProxy, HostApi,
|
ApiProxy, HostApi,
|
||||||
SkillsApi, SkillEntry,
|
|
||||||
ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
|
ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
|
||||||
MessageId, ModelReasoningEffort, ModelSelection,
|
MessageId, ModelReasoningEffort, ModelSelection,
|
||||||
RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
|
RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
|
||||||
ClientRequest, ServerResponse, RpcMessage,
|
ClientRequest, ServerResponse, RpcMessage,
|
||||||
HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk,
|
HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk,
|
||||||
SettingsApi,
|
|
||||||
ConfigurableProviderView, DiscoveredModelView, LlmApi,
|
|
||||||
} from './api.ts'
|
} from './api.ts'
|
||||||
export {
|
export {
|
||||||
RpcId,
|
RpcId,
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function apply(ctx: ClientContext): void {
|
|||||||
// One roster, four surfaces. The chip is registered in a later scope, so it
|
// One roster, four surfaces. The chip is registered in a later scope, so it
|
||||||
// subscribes here rather than being reached from this one.
|
// subscribes here rather than being reached from this one.
|
||||||
const rosterReaders = new Set<() => void>()
|
const rosterReaders = new Set<() => void>()
|
||||||
const section = new AgentPresetSectionController({ ...api, ...settingsWire }, ctx.remote, () => {
|
const section = new AgentPresetSectionController(api, ctx.remote, () => {
|
||||||
void controller.load()
|
void controller.load()
|
||||||
for (const read of rosterReaders) read()
|
for (const read of rosterReaders) read()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ClientRemote, IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
|
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 { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
||||||
import { beginRosterRead, messageOf, writeDefaultPreset } from './settings-store.ts'
|
import { beginRosterRead, messageOf, writeDefaultPreset } from './settings-store.ts'
|
||||||
|
|
||||||
@@ -134,8 +133,8 @@ export class AgentPresetSectionController {
|
|||||||
readonly store: SnapshotStore<AgentPresetSectionState> = createSnapshotStore(INITIAL)
|
readonly store: SnapshotStore<AgentPresetSectionState> = createSnapshotStore(INITIAL)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly api: SettingsWireFace & Pick<IApiClient, 'agentPresets' | 'host'>,
|
private readonly api: Pick<IApiClient, 'host'>,
|
||||||
private readonly remote: Pick<ClientRemote, 'agentPresets'>,
|
private readonly remote: Pick<ClientRemote, 'agentPresets' | 'settings'>,
|
||||||
/**
|
/**
|
||||||
* Called after this page changes the roster DIRECTORY, so the other
|
* Called after this page changes the roster DIRECTORY, so the other
|
||||||
* surfaces reading the same roster re-read it. A settings field moving is
|
* 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<void> {
|
async openLocation(id: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const response = await this.api.agentPresets.openDocument({ agentPreset: id })
|
const result = await this.remote.settings.openAgentPresetDirectory(id)
|
||||||
if (!response.result.ok) {
|
if (!result.ok) {
|
||||||
this.set({ error: response.result.error.message })
|
this.set({ error: result.error.message })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (response.result.value.opened) return
|
if (result.value.opened) return
|
||||||
const { path } = response.result.value
|
const { path } = result.value
|
||||||
this.set({ revealedPaths: { ...this.store.getSnapshot().revealedPaths, [id]: path } })
|
this.set({ revealedPaths: { ...this.store.getSnapshot().revealedPaths, [id]: path } })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.set({ error: messageOf(error) })
|
this.set({ error: messageOf(error) })
|
||||||
@@ -350,7 +349,7 @@ export class AgentPresetSectionController {
|
|||||||
* @returns once the write settled and the roster was re-read.
|
* @returns once the write settled and the roster was re-read.
|
||||||
*/
|
*/
|
||||||
async makeDefault(id: string): Promise<void> {
|
async makeDefault(id: string): Promise<void> {
|
||||||
const failure = await writeDefaultPreset(this.api, id)
|
const failure = await writeDefaultPreset(this.remote, id)
|
||||||
if (failure !== undefined) {
|
if (failure !== undefined) {
|
||||||
this.set({ error: failure })
|
this.set({ error: failure })
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -74,8 +74,7 @@
|
|||||||
"@deepseek-ai/dsh-session-stats": "workspace:^",
|
"@deepseek-ai/dsh-session-stats": "workspace:^",
|
||||||
"@deepseek-ai/dsh-settings": "workspace:^",
|
"@deepseek-ai/dsh-settings": "workspace:^",
|
||||||
"@deepseek-ai/dsh-token-meter": "workspace:^",
|
"@deepseek-ai/dsh-token-meter": "workspace:^",
|
||||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
"@deepseek-ai/dsh-tools": "workspace:^"
|
||||||
"@deepseek-ai/dsh-util-workspace-path": "workspace:^"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@deepseek-ai/cordis": "workspace:^",
|
"@deepseek-ai/cordis": "workspace:^",
|
||||||
@@ -105,7 +104,6 @@
|
|||||||
"@deepseek-ai/dsh-settings": "workspace:^",
|
"@deepseek-ai/dsh-settings": "workspace:^",
|
||||||
"@deepseek-ai/dsh-token-meter": "workspace:^",
|
"@deepseek-ai/dsh-token-meter": "workspace:^",
|
||||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||||
"@deepseek-ai/dsh-util-workspace-path": "workspace:^",
|
|
||||||
"@types/react": "~18.3.1",
|
"@types/react": "~18.3.1",
|
||||||
"react": "^18.2.0"
|
"react": "^18.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/** Register the Chat Conversation target, renderers, stats, and details surface. */
|
/** Register the Chat Conversation target, renderers, stats, and details surface. */
|
||||||
import type { Context } from '@deepseek-ai/cordis'
|
import type { Context } from '@deepseek-ai/cordis'
|
||||||
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
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 { SessionBinding } from '@deepseek-ai/dsh-api-session-controller/client'
|
||||||
import type { BoundActions, ObservableSnapshot } from '@deepseek-ai/dsh-client-store'
|
import type { BoundActions, ObservableSnapshot } from '@deepseek-ai/dsh-client-store'
|
||||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
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.
|
// 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-locale/client'
|
||||||
import type {} from '@deepseek-ai/dsh-client-ui-conversation/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. */
|
/** Services required by the Chat target and its presentation registrations. */
|
||||||
export const inject = [
|
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()
|
ctx.layout.openDetails()
|
||||||
},
|
},
|
||||||
fileMentions: (owner: TurnTailOwnerProps) => ctx.get('chatFileMentions')?.forClosing(owner),
|
fileMentions: (owner: TurnTailOwnerProps) => ctx.get('chatFileMentions')?.forClosing(owner),
|
||||||
openFile: (path) => {
|
openFile: async (path) => {
|
||||||
const cwd = ctx.sessions.list.getSnapshot().byId[sessionId]?.cwd
|
const result = await ctx.remote.session.openWorkspacePath({ sessionId, path })
|
||||||
return ctx.uiWorkspace.openPath(resolveWorkspacePath(cwd, path))
|
if (!result.ok) throw new Error(`path open failed: ${result.error.message}`)
|
||||||
},
|
},
|
||||||
loadOlder: () => { void session.loadOlder() },
|
loadOlder: () => { void session.loadOlder() },
|
||||||
loadImage: Object.assign(
|
loadImage: Object.assign(
|
||||||
|
|||||||
@@ -50,9 +50,6 @@
|
|||||||
{
|
{
|
||||||
"path": "../../runtime-diagnostics/invariants"
|
"path": "../../runtime-diagnostics/invariants"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "../../util/workspace-path"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "../../session/session-stats"
|
"path": "../../session/session-stats"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
"client": {
|
"client": {
|
||||||
"inject": [
|
"inject": [
|
||||||
"@deepseek-ai/dsh-api-session-controller",
|
"@deepseek-ai/dsh-api-session-controller",
|
||||||
"@deepseek-ai/dsh-client-connection",
|
|
||||||
"@deepseek-ai/dsh-client-locale",
|
"@deepseek-ai/dsh-client-locale",
|
||||||
"@deepseek-ai/dsh-client-ui-commands",
|
"@deepseek-ai/dsh-client-ui-commands",
|
||||||
"@deepseek-ai/dsh-api-remotes"
|
"@deepseek-ai/dsh-api-remotes"
|
||||||
@@ -49,7 +48,6 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
||||||
"@deepseek-ai/dsh-api-session-controller": "workspace:^",
|
"@deepseek-ai/dsh-api-session-controller": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
|
||||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-ui-commands": "workspace:^",
|
"@deepseek-ai/dsh-client-ui-commands": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
||||||
@@ -64,7 +62,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
||||||
"@deepseek-ai/dsh-api-session-controller": "workspace:^",
|
"@deepseek-ai/dsh-api-session-controller": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
|
||||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-ui-commands": "workspace:^",
|
"@deepseek-ai/dsh-client-ui-commands": "workspace:^",
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
/** One Host-generation model catalog shared by every Session selector. */
|
/** One Host-generation model catalog shared by every Session selector. */
|
||||||
|
|
||||||
import {
|
import type { ClientRemote, ModelCatalog } from '@deepseek-ai/dsh-api-remotes/client'
|
||||||
type IApiClient,
|
|
||||||
type ModelCatalog,
|
|
||||||
} from '@deepseek-ai/dsh-client-connection/client'
|
|
||||||
import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
||||||
|
|
||||||
/** Observable lifecycle of the shared model catalog. */
|
/** Observable lifecycle of the shared model catalog. */
|
||||||
@@ -25,8 +22,8 @@ export class ModelCatalogDirectory {
|
|||||||
private generation = 0
|
private generation = 0
|
||||||
private inflight: Promise<ModelCatalog> | undefined
|
private inflight: Promise<ModelCatalog> | undefined
|
||||||
|
|
||||||
/** @param api - shared connection API client. */
|
/** @param session - Session Remote namespace carrying the Host-generation catalog. */
|
||||||
constructor(private readonly api: IApiClient) {}
|
constructor(private readonly session: Pick<ClientRemote['session'], 'modelCatalog'>) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current generation's catalog, sharing its one in-flight load.
|
* Return the current generation's catalog, sharing its one in-flight load.
|
||||||
@@ -41,14 +38,14 @@ export class ModelCatalogDirectory {
|
|||||||
draft.status = 'loading'
|
draft.status = 'loading'
|
||||||
draft.error = null
|
draft.error = null
|
||||||
})
|
})
|
||||||
const operation = this.api.llm.models({}).then((response) => {
|
const operation = this.session.modelCatalog().then((response) => {
|
||||||
if (!response.result.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`${response.result.error.code}: ${response.result.error.message}`)
|
throw new Error(`${response.error.code}: ${response.error.message}`)
|
||||||
}
|
}
|
||||||
if (generation === this.generation) {
|
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) => {
|
}).catch((error: unknown) => {
|
||||||
if (generation === this.generation) {
|
if (generation === this.generation) {
|
||||||
this.store.update((draft) => {
|
this.store.update((draft) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Model selection plugin, browser half — TWO entries over ONE per-session
|
* Model selection plugin, browser half — TWO entries over ONE per-session
|
||||||
* directory owned by ModelDirectoryResolver (`ctx.modelDirectories`). The /model popupSelect
|
* directory owned by ModelDirectoryResolver (`ctx.modelDirectories`). The /model popupSelect
|
||||||
* contribution and the composer's named `conversation.input.model` seat share
|
* 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`.
|
* durable model-selection projection, and submit through `session.selectModel`.
|
||||||
* A switch made in either entry is what the other shows next. Failures
|
* 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
|
* ride each entry's own retry surface (popup shell error/retry; seat menu
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
import { Service } from '@deepseek-ai/cordis'
|
import { Service } from '@deepseek-ai/cordis'
|
||||||
import type { Context } from '@deepseek-ai/cordis'
|
import type { Context } from '@deepseek-ai/cordis'
|
||||||
import type {} from '@deepseek-ai/dsh-api-session-controller/client'
|
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 type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||||
import { ModelCatalogDirectory } from './catalog.ts'
|
import { ModelCatalogDirectory } from './catalog.ts'
|
||||||
import { ModelDirectory } from './directory.ts'
|
import { ModelDirectory } from './directory.ts'
|
||||||
@@ -34,7 +33,7 @@ interface LiveState {
|
|||||||
|
|
||||||
/** The `ctx.modelDirectories` session model-selection service. */
|
/** The `ctx.modelDirectories` session model-selection service. */
|
||||||
export class ModelDirectoryResolver extends 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 live: LiveState = { directories: new Map() }
|
||||||
private readonly catalog: ModelCatalogDirectory
|
private readonly catalog: ModelCatalogDirectory
|
||||||
@@ -49,9 +48,7 @@ export class ModelDirectoryResolver extends Service {
|
|||||||
constructor(ctx: Context, config: { blockReason: () => string }) {
|
constructor(ctx: Context, config: { blockReason: () => string }) {
|
||||||
super(ctx, 'modelDirectories')
|
super(ctx, 'modelDirectories')
|
||||||
this.blockReason = config.blockReason
|
this.blockReason = config.blockReason
|
||||||
const connection = ctx.get('connection') as ConnectionHandle | undefined
|
this.catalog = new ModelCatalogDirectory(ctx.remote.session)
|
||||||
if (connection === undefined) throw new Error('ui-model-selection: connection service is unavailable')
|
|
||||||
this.catalog = new ModelCatalogDirectory(connection.api)
|
|
||||||
void this.catalog.load().catch(() => { /* selectors expose the shared error */ })
|
void this.catalog.load().catch(() => { /* selectors expose the shared error */ })
|
||||||
ctx.on('connection/reset', () => {
|
ctx.on('connection/reset', () => {
|
||||||
this.catalog.resetGeneration()
|
this.catalog.resetGeneration()
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const NS = 'settings'
|
|||||||
* ui-settings' apply, whose activation order relative to this one is NOT
|
* ui-settings' apply, whose activation order relative to this one is NOT
|
||||||
* constrained; registrations depend on their slots through `slots.inject()`.
|
* 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
|
* 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
|
const connection = ctx.get('connection') as ConnectionHandle
|
||||||
// The shared SettingsScope mirror updates after document commits and reconnects.
|
// The shared SettingsScope mirror updates after document commits and reconnects.
|
||||||
const documentController = connection.isLoopback
|
const documentController = connection.isLoopback
|
||||||
? new SettingsDocumentStore(connection.api, ctx.settingsScope.describe())
|
? new SettingsDocumentStore(ctx.remote, ctx.settingsScope.describe())
|
||||||
: undefined
|
: undefined
|
||||||
const documentInjected = documentController === undefined
|
const documentInjected = documentController === undefined
|
||||||
? undefined
|
? undefined
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/** State owner for the optional local settings-document action. */
|
/** 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 { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
||||||
import type { SettingsDescribeFace } from '@deepseek-ai/dsh-client-ui-settings/client'
|
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).
|
* @param describeFace - the shared mirror's describe face (`hasDocument` source).
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly api: Pick<IApiClient, 'settings'>,
|
private readonly remote: Pick<ClientRemote, 'settings'>,
|
||||||
private readonly describeFace: SettingsDescribeFace,
|
private readonly describeFace: SettingsDescribeFace,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -63,8 +63,8 @@ export class SettingsDocumentStore {
|
|||||||
state.error = null
|
state.error = null
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
const response = await this.api.settings.openDocument({})
|
const result = await this.remote.settings.openSettingsDocument()
|
||||||
if (!response.result.ok) throw new Error(response.result.error.message)
|
if (!result.ok) throw new Error(result.error.message)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.store.update((state) => { state.error = messageOf(error) })
|
this.store.update((state) => { state.error = messageOf(error) })
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -47,7 +47,6 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@deepseek-ai/cordis": "workspace:^",
|
"@deepseek-ai/cordis": "workspace:^",
|
||||||
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
|
||||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-ui-settings": "workspace:^",
|
"@deepseek-ai/dsh-client-ui-settings": "workspace:^",
|
||||||
@@ -55,7 +54,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
|
||||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-store": "workspace:^",
|
"@deepseek-ai/dsh-client-store": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
||||||
|
|||||||
@@ -16,11 +16,11 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import type { ReactNode } 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 { Button, Modal } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||||
import { formatCapacity, parseCapacity } from './DeepSeekModelsEditor.tsx'
|
import { formatCapacity, parseCapacity } from './DeepSeekModelsEditor.tsx'
|
||||||
import type { DeepSeekModelDraft } 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 type { en } from './locales.ts'
|
||||||
import styles from './ModelsSection.module.css'
|
import styles from './ModelsSection.module.css'
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ export interface ModelListEditorProps {
|
|||||||
*/
|
*/
|
||||||
probeBlocked?: keyof typeof en | undefined
|
probeBlocked?: keyof typeof en | undefined
|
||||||
/** Wire face the fetch action calls. */
|
/** Wire face the fetch action calls. */
|
||||||
api: Pick<IApiClient, 'llm'>
|
api: Pick<ModelsWire, 'llm'>
|
||||||
/** Section copy. */
|
/** Section copy. */
|
||||||
t: (key: keyof typeof en) => string
|
t: (key: keyof typeof en) => string
|
||||||
/** Disable every control (read-only deployment or a pending write). */
|
/** 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. */
|
/** Adopt a candidate, keeping whatever capacities the provider disclosed. */
|
||||||
function adopt(candidate: DiscoveredModelView): ModelDraft {
|
function adopt(candidate: LlmDiscoveredModel): ModelDraft {
|
||||||
return {
|
return {
|
||||||
id: candidate.id,
|
id: candidate.id,
|
||||||
...candidate.name === undefined ? {} : { name: candidate.name },
|
...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 { models, onChange, probe, api, t, disabled } = props
|
||||||
const [busy, setBusy] = useState(false)
|
const [busy, setBusy] = useState(false)
|
||||||
const [failure, setFailure] = useState<string | undefined>(undefined)
|
const [failure, setFailure] = useState<string | undefined>(undefined)
|
||||||
const [candidates, setCandidates] = useState<readonly DiscoveredModelView[] | undefined>(undefined)
|
const [candidates, setCandidates] = useState<readonly LlmDiscoveredModel[] | undefined>(undefined)
|
||||||
const [picked, setPicked] = useState<ReadonlySet<string>>(new Set())
|
const [picked, setPicked] = useState<ReadonlySet<string>>(new Set())
|
||||||
// Rows carry an id and a name; capacities are the exception, so they stay
|
// 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.
|
// folded until asked for rather than crowding every row with four inputs.
|
||||||
@@ -229,18 +229,17 @@ export function ModelListEditor(props: ModelListEditorProps): ReactNode {
|
|||||||
setBusy(true)
|
setBusy(true)
|
||||||
setFailure(undefined)
|
setFailure(undefined)
|
||||||
try {
|
try {
|
||||||
const response = await api.llm.discoverModels({
|
const response = await api.llm.discoverModels(probe.settingsNs, {
|
||||||
settingsNs: probe.settingsNs,
|
|
||||||
...probe.provider === undefined ? {} : { provider: probe.provider },
|
...probe.provider === undefined ? {} : { provider: probe.provider },
|
||||||
...probe.baseURL === undefined || probe.baseURL.length === 0 ? {} : { baseURL: probe.baseURL },
|
...probe.baseURL === undefined || probe.baseURL.length === 0 ? {} : { baseURL: probe.baseURL },
|
||||||
...probe.api === undefined ? {} : { api: probe.api },
|
...probe.api === undefined ? {} : { api: probe.api },
|
||||||
...probe.apiKey === undefined ? {} : { apiKey: probe.apiKey },
|
...probe.apiKey === undefined ? {} : { apiKey: probe.apiKey },
|
||||||
})
|
})
|
||||||
if (!response.result.ok) {
|
if (!response.ok) {
|
||||||
setFailure(response.result.error.message)
|
setFailure(response.error.message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const found = response.result.value.models
|
const found = response.value
|
||||||
if (found.length === 0) {
|
if (found.length === 0) {
|
||||||
setFailure(t('fetchEmpty'))
|
setFailure(t('fetchEmpty'))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
* packages/client/AGENTS.md.
|
* packages/client/AGENTS.md.
|
||||||
*/
|
*/
|
||||||
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
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).
|
// Type-only: pulls the shell's SlotMap merge (the 'settings.section' entry).
|
||||||
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
||||||
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
|
// 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. */
|
/** Dictionary namespace owned by this plugin. */
|
||||||
const NS = 'settings.models'
|
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
|
* 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()`.
|
* constrained; registration depends on each slot through `slots.inject()`.
|
||||||
*/
|
*/
|
||||||
export const inject = [
|
export const inject = [
|
||||||
'slots', 'locale', 'connection', 'remote', 'remote.credentials', 'remote.settings',
|
'slots', 'locale', 'remote', 'remote.credentials', 'remote.llm', 'remote.settings',
|
||||||
'settingsScope', 'settingsSchema',
|
'settingsScope', 'settingsSchema',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -73,14 +74,11 @@ export const inject = [
|
|||||||
export function apply(ctx: ClientContext): void {
|
export function apply(ctx: ClientContext): void {
|
||||||
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-models: copy dictionaries')
|
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)
|
const schema = createSettingsSchemaOperations(ctx.settingsSchema)
|
||||||
// The page's two carriers under one face: model discovery and the catalog
|
// Every configuration operation rides its owning Remote namespace.
|
||||||
// still ride the unary API, while settings and credentials are Remote
|
|
||||||
// namespaces.
|
|
||||||
const wire: ModelsWire = {
|
const wire: ModelsWire = {
|
||||||
...connection.api,
|
|
||||||
credentials: ctx.remote.credentials,
|
credentials: ctx.remote.credentials,
|
||||||
|
llm: ctx.remote.llm,
|
||||||
settings: ctx.remote.settings,
|
settings: ctx.remote.settings,
|
||||||
}
|
}
|
||||||
const controller = new ModelsSettingsStore(wire, schema, ctx.settingsScope.describe())
|
const controller = new ModelsSettingsStore(wire, schema, ctx.settingsScope.describe())
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* without editing it.
|
* without editing it.
|
||||||
*
|
*
|
||||||
* `settings.models.provider-card` is keyed by the row's owning settings
|
* `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
|
* companion plugin registers one entry under the family's namespace and
|
||||||
* receives every card of that family — shipped, added, and hand-declared rows
|
* receives every card of that family — shipped, added, and hand-declared rows
|
||||||
* alike — while the section never learns what the namespace means. Keying on
|
* 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.
|
* 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 {} from '@deepseek-ai/dsh-client-ui-slots'
|
||||||
|
import type { ProviderDirectoryEntry } from './store.ts'
|
||||||
|
|
||||||
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||||
interface SlotMap {
|
interface SlotMap {
|
||||||
@@ -42,7 +42,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|||||||
/** Owner share of one provider-card extension occurrence. */
|
/** Owner share of one provider-card extension occurrence. */
|
||||||
export interface ProviderCardExtrasOwnerProps {
|
export interface ProviderCardExtrasOwnerProps {
|
||||||
/** The card's directory row (route id, display name, settings address, live state). */
|
/** 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. */
|
/** Whether any layer configures this provider (its profile resolves); `false` while the add-provider draft edits a dormant row. */
|
||||||
configured: boolean
|
configured: boolean
|
||||||
/** Whether the row's referenced api-key credential is confirmed configured (the page's credential join). */
|
/** Whether the row's referenced api-key credential is confirmed configured (the page's credential join). */
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* Models settings page store: one snapshot joining the configurable-provider
|
* 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
|
* and the referenced credentials (`credentials/describe`). The host stays the
|
||||||
* single fact source — every mutation writes through the wire and the page
|
* single fact source — every mutation writes through the wire and the page
|
||||||
* re-renders from the next describe, pushed or refetched.
|
* re-renders from the next describe, pushed or refetched.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ClientRemote, ConfigurableProviderView, CredentialInfo, IApiClient, SettingsNamespaceView,
|
ClientRemote, CredentialInfo, LlmConfigurableProvider, LlmProviderInfo, SettingsNamespaceView,
|
||||||
} from '@deepseek-ai/dsh-api-remotes/client'
|
} from '@deepseek-ai/dsh-api-remotes/client'
|
||||||
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
||||||
import { createSnapshotStore } 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. */
|
/** The credentials Remote methods the Models page reads and writes through. */
|
||||||
export type ModelsCredentials = Pick<ClientRemote['credentials'], 'describe' | 'set' | 'unset'>
|
export type ModelsCredentials = Pick<ClientRemote['credentials'], 'describe' | 'set' | 'unset'>
|
||||||
|
|
||||||
|
/** 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,
|
* Join declared configurable providers with the currently registered routes.
|
||||||
* plus the credentials Remote namespace, which is addressed by reference name
|
* @param registered - live provider routes in registration order.
|
||||||
* and never answers with a value.
|
* @param directory - declared configurable providers in declaration order.
|
||||||
|
* @returns declared rows followed by live routes with no declaration.
|
||||||
*/
|
*/
|
||||||
export interface ModelsWire extends Pick<IApiClient, 'llm'> {
|
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. */
|
/** The settings Remote namespace: the redacted read and the profile writes. */
|
||||||
settings: SettingsRemote
|
settings: SettingsRemote
|
||||||
/** Credential state and writes for the references provider profiles name. */
|
/** Credential state and writes for the references provider profiles name. */
|
||||||
credentials: ModelsCredentials
|
credentials: ModelsCredentials
|
||||||
|
/** Provider directory reads and draft endpoint discovery. */
|
||||||
|
llm: ModelsLlm
|
||||||
}
|
}
|
||||||
|
|
||||||
/** One provider row the page renders. */
|
/** One provider row the page renders. */
|
||||||
export interface ProviderRow {
|
export interface ProviderRow {
|
||||||
/** The directory entry (route id, display name, settings address, live state). */
|
/** The directory entry (route id, display name, settings address, live state). */
|
||||||
entry: ConfigurableProviderView
|
entry: ProviderDirectoryEntry
|
||||||
/** Whether any layer configures this provider (its profile resolves). */
|
/** Whether any layer configures this provider (its profile resolves). */
|
||||||
configured: boolean
|
configured: boolean
|
||||||
/** Whether the user layer alone carries the profile (removal restores the base). */
|
/** Whether the user layer alone carries the profile (removal restores the base). */
|
||||||
@@ -157,20 +207,22 @@ export class ModelsSettingsStore {
|
|||||||
async load(): Promise<void> {
|
async load(): Promise<void> {
|
||||||
const generation = ++this.generation
|
const generation = ++this.generation
|
||||||
this.store.update((s) => { s.status = 'loading'; s.error = null })
|
this.store.update((s) => { s.status = 'loading'; s.error = null })
|
||||||
let providers: ConfigurableProviderView[]
|
let providers: ProviderDirectoryEntry[]
|
||||||
let writable: boolean
|
let writable: boolean
|
||||||
let views: readonly SettingsNamespaceView[]
|
let views: readonly SettingsNamespaceView[]
|
||||||
try {
|
try {
|
||||||
const [providersResponse] = await Promise.all([
|
const [registered, declared] = await Promise.all([
|
||||||
this.api.llm.providers({}),
|
this.api.llm.listProviders(),
|
||||||
|
this.api.llm.listConfigurableProviders(),
|
||||||
this.describeFace.ensure(),
|
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()
|
const mirrored = this.describeFace.getSnapshot()
|
||||||
if (mirrored.view === undefined) {
|
if (mirrored.view === undefined) {
|
||||||
throw new Error(mirrored.error ?? 'settings are unavailable in this browser')
|
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
|
writable = mirrored.view.writable
|
||||||
views = mirrored.view.namespaces
|
views = mirrored.view.namespaces
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
* settings scope, which keeps them unaware of one another and of other tabs.
|
* 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).
|
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
|
||||||
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
||||||
// Type-only: the settings shell's SlotMap merge (the 'settings.section' entry)
|
// 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'
|
const NS = 'settings.plugins'
|
||||||
|
|
||||||
/** Required services (cordis fiber inject). */
|
/** 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.
|
* Mount the plugin configuration section and the cards this package ships.
|
||||||
* @param ctx - the browser plugin context.
|
* @param ctx - the browser plugin context.
|
||||||
*/
|
*/
|
||||||
export function apply(ctx: ClientContext): void {
|
export function apply(ctx: ClientContext): void {
|
||||||
const { api } = ctx.get('connection') as ConnectionHandle
|
|
||||||
const t = ctx.locale.bind(NS)
|
const t = ctx.locale.bind(NS)
|
||||||
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-plugins: section dictionaries')
|
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)
|
ctx.settingsScope.bind({ namespace: WEB_SEARCH_NS }), ctx.remote.credentials)
|
||||||
const subagentModelSelection = new SubagentModelSelectionCardController(
|
const subagentModelSelection = new SubagentModelSelectionCardController(
|
||||||
ctx.settingsScope.bind({ namespace: SUBAGENT_MODEL_SELECTION_NS }),
|
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
|
// The credential a card reports is not part of any settings section, so its
|
||||||
|
|||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
/** Staged editor for the Host-owned subagent model allowlist. */
|
/** Staged editor for the Host-owned subagent model allowlist. */
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
IApiClient,
|
ClientRemote,
|
||||||
ModelProviderGroup,
|
ModelProviderGroup,
|
||||||
} from '@deepseek-ai/dsh-api-remotes/client'
|
} from '@deepseek-ai/dsh-api-remotes/client'
|
||||||
import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
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 scope - bound `subagent-model-selection` settings scope.
|
||||||
* @param api - Host LLM directory face.
|
* @param session - Host Session model-catalog face.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly scope: SettingsScope<SubagentModelSelectionSettings>,
|
private readonly scope: SettingsScope<SubagentModelSelectionSettings>,
|
||||||
private readonly api: Pick<IApiClient, 'llm'>,
|
private readonly session: Pick<ClientRemote['session'], 'modelCatalog'>,
|
||||||
) {
|
) {
|
||||||
this.store = createSnapshotStore(this.projection())
|
this.store = createSnapshotStore(this.projection())
|
||||||
this.unsubscribe = scope.subscribe(() => {
|
this.unsubscribe = scope.subscribe(() => {
|
||||||
@@ -321,11 +321,11 @@ export class SubagentModelSelectionCardController {
|
|||||||
this.catalogPartial = false
|
this.catalogPartial = false
|
||||||
this.publish()
|
this.publish()
|
||||||
try {
|
try {
|
||||||
const response = await this.api.llm.models({})
|
const response = await this.session.modelCatalog()
|
||||||
if (generation !== this.catalogGeneration) return
|
if (generation !== this.catalogGeneration) return
|
||||||
if (!response.result.ok) throw new Error(response.result.error.message)
|
if (!response.ok) throw new Error(response.error.message)
|
||||||
this.catalogGroups = response.result.value.groups
|
this.catalogGroups = response.value.groups
|
||||||
this.catalogPartial = response.result.value.failures.length > 0
|
this.catalogPartial = response.value.failures.length > 0
|
||||||
this.catalogStatus = 'ready'
|
this.catalogStatus = 'ready'
|
||||||
} catch {
|
} catch {
|
||||||
if (generation !== this.catalogGeneration) return
|
if (generation !== this.catalogGeneration) return
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Skill reference plugin, browser half: registers the '/' skill source —
|
* 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
|
* projection's sessionId (sessions are always agent-backed; the host
|
||||||
* resolves cwd from the session header). A pick lands the literal `/name `
|
* resolves cwd from the session header). A pick lands the literal `/name `
|
||||||
* text and the prompt ships the same literal (plain-text-reference decision;
|
* 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
|
* leading `/name` naming a user-invocable skill and injects the rendered
|
||||||
* body for every entry point, including `disable-model-invocation` skills the
|
* body for every entry point, including `disable-model-invocation` skills the
|
||||||
* model-side catalog never lists (issue #1470). The RPC rides the plugin's
|
* 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
|
* services off a per-call argument. Draft chip visuals derive from
|
||||||
* the lexicon scan; this source implements no reference codec.
|
* 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.
|
// 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 { 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 {} from '@deepseek-ai/dsh-api-session-controller/client'
|
||||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||||
import type { InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
import type { InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
||||||
@@ -71,7 +71,7 @@ export function apply(ctx: ClientContext): void {
|
|||||||
SkillRow,
|
SkillRow,
|
||||||
))
|
))
|
||||||
|
|
||||||
const skills = (ctx.get('connection') as ConnectionHandle).api.skills
|
const skills = ctx.remote.skills
|
||||||
const sessions = ctx.sessions
|
const sessions = ctx.sessions
|
||||||
// Session-keyed catalog cache; single-flight per key. Plugin-closure state:
|
// Session-keyed catalog cache; single-flight per key. Plugin-closure state:
|
||||||
// the fiber effect below is its teardown boundary.
|
// the fiber effect below is its teardown boundary.
|
||||||
@@ -98,8 +98,8 @@ export function apply(ctx: ClientContext): void {
|
|||||||
if (existing !== undefined) return existing.promise
|
if (existing !== undefined) return existing.promise
|
||||||
const abort = new AbortController()
|
const abort = new AbortController()
|
||||||
const promise = (async () => {
|
const promise = (async () => {
|
||||||
const { result } = await skills.list({ sessionId }, abort.signal)
|
const result = await skills.list({ sessionId }, abort.signal)
|
||||||
if (!result.ok) throw new Error(`skill.list failed: ${result.error.code}: ${result.error.message}`)
|
if (!result.ok) throw new Error(`skills/list failed: ${result.error.code}: ${result.error.message}`)
|
||||||
return result.value.skills
|
return result.value.skills
|
||||||
})()
|
})()
|
||||||
const entry: CatalogFetch = { promise, abort }
|
const entry: CatalogFetch = { promise, abort }
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function apply(ctx: Context): void {
|
|||||||
const workspaces = ctx.get('workspaces') as IWorkspaces
|
const workspaces = ctx.get('workspaces') as IWorkspaces
|
||||||
const hostDescription = connection.hostDescription
|
const hostDescription = connection.hostDescription
|
||||||
const uiWorkspace = new UiWorkspaceService(
|
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.slots.provideRoot({ hooks: { workspaces: workspaces.list } })
|
||||||
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-workspace: dictionaries')
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-workspace: dictionaries')
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/** Workspace archive and directory UI capability. */
|
/** Workspace archive and directory UI capability. */
|
||||||
|
|
||||||
import { Service, type Context } from '@deepseek-ai/cordis'
|
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 { ClientRemote, DirectoryListing } from '@deepseek-ai/dsh-api-remotes/client'
|
||||||
import type { RemoteFailure } from '@deepseek-ai/dsh-typert-protocol'
|
import type { RemoteFailure } from '@deepseek-ai/dsh-typert-protocol'
|
||||||
import type {
|
import type {
|
||||||
@@ -50,11 +49,6 @@ export interface UiWorkspace {
|
|||||||
* @returns created absolute path.
|
* @returns created absolute path.
|
||||||
*/
|
*/
|
||||||
createDirectory(path: string, name: string): Promise<string>
|
createDirectory(path: string, name: string): Promise<string>
|
||||||
/**
|
|
||||||
* Open a path with the Host operating system.
|
|
||||||
* @param path - absolute or Host-resolvable path.
|
|
||||||
*/
|
|
||||||
openPath(path: string): Promise<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@deepseek-ai/cordis' {
|
declare module '@deepseek-ai/cordis' {
|
||||||
@@ -80,14 +74,12 @@ class UiWorkspaceService extends Service implements UiWorkspace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ctx - Client root Context.
|
* @param ctx - Client root Context.
|
||||||
* @param api - shared Host API carrier.
|
|
||||||
* @param directoryPicker - the directory-picking Remote namespace.
|
* @param directoryPicker - the directory-picking Remote namespace.
|
||||||
* @param workspaces - pure Workspace Controller.
|
* @param workspaces - pure Workspace Controller.
|
||||||
* @param sessions - pure Session Controller.
|
* @param sessions - pure Session Controller.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
private readonly api: IApiClient,
|
|
||||||
private readonly directoryPicker: ClientRemote['directoryPicker'],
|
private readonly directoryPicker: ClientRemote['directoryPicker'],
|
||||||
private readonly workspaces: IWorkspaces,
|
private readonly workspaces: IWorkspaces,
|
||||||
private readonly sessions: ISessions,
|
private readonly sessions: ISessions,
|
||||||
@@ -163,13 +155,6 @@ class UiWorkspaceService extends Service implements UiWorkspace {
|
|||||||
return result.value
|
return result.value
|
||||||
}
|
}
|
||||||
|
|
||||||
async openPath(path: string): Promise<void> {
|
|
||||||
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 {
|
private watchNavigation(): () => void {
|
||||||
let initial: 'waiting' | 'connecting' | 'done' = 'waiting'
|
let initial: 'waiting' | 'connecting' | 'done' = 'waiting'
|
||||||
let disposed = false
|
let disposed = false
|
||||||
|
|||||||
Reference in New Issue
Block a user