mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml # docs/core-data-structures/core.i18n.yaml # docs/module-graph.md # packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx # packages/client/ui-conversation/src/client/index.ts # packages/compact/compact-basic/README.i18n.yaml
This commit is contained in:
@@ -20,6 +20,7 @@ import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence'
|
||||
import { SessionQueryError, type SessionSearchCursor } from '@deepseek-ai/dsh-session-query'
|
||||
import { SubagentError } from '@deepseek-ai/dsh-subagent'
|
||||
import type { SubagentListEntry as CatalogSubagentListEntry } from '@deepseek-ai/dsh-subagent'
|
||||
import { isUserInvocable } from '@deepseek-ai/dsh-skill'
|
||||
import type { Workspace, WorkspaceRecord } from '@deepseek-ai/dsh-workspace'
|
||||
import {
|
||||
workspaceDomainState, workspaceRecord, WorkspaceId as brandWorkspaceId,
|
||||
@@ -1363,6 +1364,34 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
return llm === undefined || llm.listProviders().some(entry => entry.id === provider)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the addressed agent for a turn-starting method and refuse when no
|
||||
* adapter serves its current route: a route nothing serves cannot start a
|
||||
* turn, and letting it try spends the whole pre-step path to fail inside
|
||||
* the adapter with a message about registration. Refusing here names the
|
||||
* model the session is pointed at while the draft is still in the composer.
|
||||
* This is `session.prompt`'s enforcement boundary: a client that disables
|
||||
* its input is an affordance, and the method stays callable regardless.
|
||||
*/
|
||||
async function turnAgentFor<T>(
|
||||
request: RpcRequest<unknown>, sessionId: SessionId,
|
||||
): Promise<{ agent: Agent } | { refused: RpcResponse<T> }> {
|
||||
const found = await agentFor(sessionId)
|
||||
if ('error' in found) return { refused: err(request, found.error) }
|
||||
const agent = found.agent
|
||||
const target = targetFor(agent).current
|
||||
if (!routeServed(target.provider)) {
|
||||
return {
|
||||
refused: err(request, {
|
||||
code: 'model-unavailable',
|
||||
message: `no adapter serves provider "${target.provider}"; select a model for this session`,
|
||||
details: { provider: target.provider, model: target.model },
|
||||
}),
|
||||
}
|
||||
}
|
||||
return { agent }
|
||||
}
|
||||
|
||||
/** Missing-service report shared by the settings domain (skills-domain stance). */
|
||||
function settingsAbsent(): RpcError {
|
||||
return { code: 'internal', message: 'settings service is absent: this deployment does not mount a settings provider (e.g. @deepseek-ai/dsh-settings-local) in its composition', details: {} }
|
||||
@@ -1909,23 +1938,9 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
|
||||
async prompt(request) {
|
||||
const { sessionId, mode, content } = request.payload
|
||||
const found = await agentFor(sessionId)
|
||||
if ('error' in found) return err(request, found.error)
|
||||
const agent = found.agent
|
||||
// A route no adapter serves cannot start a turn, and letting it try
|
||||
// spends the whole pre-step path to fail inside the adapter with a
|
||||
// message about registration. Refusing here names the model the
|
||||
// session is pointed at while the draft is still in the composer.
|
||||
// This is the enforcement boundary: a client that disables its input
|
||||
// is an affordance, and this method stays callable regardless.
|
||||
const target = targetFor(agent).current
|
||||
if (!routeServed(target.provider)) {
|
||||
return err(request, {
|
||||
code: 'model-unavailable',
|
||||
message: `no adapter serves provider "${target.provider}"; select a model for this session`,
|
||||
details: { provider: target.provider, model: target.model },
|
||||
})
|
||||
}
|
||||
const resolved = await turnAgentFor<{ accepted: true }>(request, sessionId)
|
||||
if ('refused' in resolved) return resolved.refused
|
||||
const agent = resolved.agent
|
||||
// The rpcId rides MessageSource into user/message (merge declaration in api/sessions.ts; provisional correlation).
|
||||
const source: MessageSource = { kind: 'user', rpcId: request.rpcId }
|
||||
const hasImage = content.some(part => part.type === 'image')
|
||||
@@ -2568,13 +2583,13 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
return err(request, { code: 'internal', message: 'skill registry is absent: this deployment does not mount @deepseek-ai/dsh-skill in its composition (cordis.yml or explicit assembly)', details: {} })
|
||||
}
|
||||
try {
|
||||
const skills = (await skillRegistry.list({ cwd }))
|
||||
.filter(skill => skill.invocation.modelInvocable && skill.invocation.userInvocable)
|
||||
const skills = (await skillRegistry.list({ cwd })).filter(isUserInvocable)
|
||||
return ok(request, {
|
||||
skills: skills.map(skill => ({
|
||||
name: skill.name,
|
||||
description: skill.description,
|
||||
...skill.whenToUse === undefined ? {} : { whenToUse: skill.whenToUse },
|
||||
modelInvocable: skill.invocation.modelInvocable,
|
||||
})),
|
||||
})
|
||||
} catch (error: unknown) {
|
||||
|
||||
Reference in New Issue
Block a user