Merge branch 'master' of https://github.com/deepseek-harness/deepseek-harness into xtr/react-loop-simplification
# Conflicts: # .agents/notes/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml # docs/architecture.i18n.yaml # docs/cookbook/extension-cookbook.i18n.yaml # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.i18n.yaml # docs/core-data-structures/core.md # docs/core-data-structures/core.zh.md # docs/core-data-structures/llm-streaming.i18n.yaml # docs/core-data-structures/llm-streaming.md # docs/core-data-structures/llm-streaming.zh.md # docs/core-data-structures/session.i18n.yaml # docs/event-producer-consumer.md # docs/persistence-catalog.md # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-loop/README.i18n.yaml # packages/core/agent-loop/src/agent.ts # packages/core/agent/README.i18n.yaml # packages/core/session/README.i18n.yaml # packages/core/session/src/types.ts # packages/llm/llm/README.i18n.yaml # packages/llm/llm/README.md # packages/llm/llm/README.zh.md # packages/llm/llm/src/index.ts # packages/llm/llm/tests/service.spec.ts # packages/sdk/sdk-client/README.i18n.yaml # packages/sdk/sdk-protocol/README.i18n.yaml # packages/sdk/sdk-protocol/README.md # packages/sdk/sdk-protocol/README.zh.md # packages/subagent/subagent-dsh-sdk/README.i18n.yaml # packages/ui/jsonrpc/README.i18n.yaml # packages/ui/jsonrpc/README.md # packages/ui/jsonrpc/README.zh.md # packages/ui/tui/src/index.ts # python/sdk/README.i18n.yaml # scripts/gen-cordis-catalog.ts
This commit is contained in:
@@ -25,7 +25,7 @@ import {
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
import type { Scope } from '@deepseek-ai/dsh-scope'
|
||||
import { createScope } from '@deepseek-ai/dsh-scope'
|
||||
import type { Session, SessionId, TurnEndReason, UserMessage } from '@deepseek-ai/dsh-session'
|
||||
import type { EpochHeader, Session, SessionId, TurnEndReason, UserMessage } from '@deepseek-ai/dsh-session'
|
||||
import { canonicalHeader, headerEquals } from '@deepseek-ai/dsh-session'
|
||||
import { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
|
||||
import type { Context } from 'cordis'
|
||||
@@ -41,6 +41,15 @@ type Admission =
|
||||
| { kind: 'admitted'; messages: UserMessage[] }
|
||||
| { kind: 'blocked' }
|
||||
|
||||
/** Remove adapter-derived values before plugins propose the next request config. */
|
||||
function requestProposal(header: EpochHeader): LlmCallConfig {
|
||||
if (header.adapterDefaults === undefined) return header.config
|
||||
const proposal = { ...header.config }
|
||||
if (header.adapterDefaults.reasoningEffort === true) delete proposal.reasoningEffort
|
||||
if (header.adapterDefaults.maxTokens === true) delete proposal.maxTokens
|
||||
return proposal
|
||||
}
|
||||
|
||||
/** Drives one session through turn and step boundaries. */
|
||||
export class ReactLoopAgent implements Agent {
|
||||
readonly inbox: Inbox
|
||||
@@ -309,21 +318,29 @@ export class ReactLoopAgent implements Agent {
|
||||
boundaryMessages: Message[],
|
||||
signal: AbortSignal,
|
||||
): Promise<{ request: GenerateOptions; preparedCall?: PreparedLlmCall }> {
|
||||
const persistedConfig = this.session.requestHeader()?.config
|
||||
const { session } = this
|
||||
|
||||
// A loop instance starts from its declared route, restoring only an explicit
|
||||
// effort owned by that exact model. Later steps re-resolve marked defaults.
|
||||
const persistedHeader = session.requestHeader()
|
||||
const persistedConfig = persistedHeader?.config
|
||||
const route = { provider: this.options.provider ?? '', model: this.options.model ?? '' }
|
||||
const reasoningEffort = persistedConfig?.provider === route.provider
|
||||
&& persistedConfig.model === route.model
|
||||
&& persistedHeader?.adapterDefaults?.reasoningEffort !== true
|
||||
? persistedConfig.reasoningEffort
|
||||
: undefined
|
||||
const maxTokens = this.options.maxTokens
|
||||
const seedConfig = this.requestHeaderLogged
|
||||
// oxlint-disable-next-line typescript/no-non-null-assertion -- the instance logged the frozen header it now folds
|
||||
? persistedConfig!
|
||||
: deepFreeze({
|
||||
...route,
|
||||
...reasoningEffort === undefined ? {} : { reasoningEffort },
|
||||
...maxTokens === undefined ? {} : { maxTokens },
|
||||
})
|
||||
const seedConfig = deepFreeze(structuredClone(
|
||||
this.requestHeaderLogged
|
||||
// oxlint-disable-next-line typescript/no-non-null-assertion -- the instance logged the header it now folds
|
||||
? requestProposal(persistedHeader!)
|
||||
: {
|
||||
...route,
|
||||
...reasoningEffort === undefined ? {} : { reasoningEffort },
|
||||
...maxTokens === undefined ? {} : { maxTokens },
|
||||
},
|
||||
))
|
||||
const proposedConfig = await this.loopCtx.waterfall(
|
||||
agentCarrier(this), 'agent/request', this, turn, step, signal,
|
||||
() => Promise.resolve(seedConfig),
|
||||
@@ -346,6 +363,7 @@ export class ReactLoopAgent implements Agent {
|
||||
|
||||
const header = canonicalHeader({
|
||||
config,
|
||||
...preparedCall === undefined ? {} : { adapterDefaults: preparedCall.adapterDefaults },
|
||||
...system ? { system } : {},
|
||||
...tools.length > 0 ? { tools } : {},
|
||||
})
|
||||
|
||||
@@ -134,6 +134,15 @@ interface PreparedAgent {
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
agentLoop: AgentLoop
|
||||
/**
|
||||
* Launcher-owned exact session identities for configured agents, keyed by
|
||||
* the agent's config `id` and set with `ctx.provide()` before any Loader
|
||||
* entry mounts (see {@link CONFIGURED_AGENT_IDENTITIES_KEY}). A launcher
|
||||
* owns identity because only it knows whether the session already exists,
|
||||
* while the `cordis.yml` row keeps the model route as ordinary patchable
|
||||
* config. An entry with no matching key keeps its configured identity.
|
||||
*/
|
||||
configuredAgentIdentities?: ConfiguredAgentIdentities
|
||||
}
|
||||
interface Events {
|
||||
/**
|
||||
@@ -151,6 +160,53 @@ declare module 'cordis' {
|
||||
|
||||
export { DEFAULT_MAX_PARALLEL_TOOL_CALLS }
|
||||
|
||||
/**
|
||||
* One launcher-selected session identity for a configured agent. `resume`
|
||||
* distinguishes rehydrating existing persisted history from creating the
|
||||
* session fresh under that exact id, which the two config keys express as
|
||||
* `resumeSessionId` and `sessionId`.
|
||||
*/
|
||||
export interface LauncherAgentIdentity {
|
||||
/** Exact session id to create fresh or resume. */
|
||||
id: SessionId
|
||||
/** Resume existing persisted history instead of creating the session fresh. */
|
||||
resume: boolean
|
||||
}
|
||||
|
||||
/** Launcher-selected identities keyed by the configured agent's `id`. */
|
||||
export interface ConfiguredAgentIdentities extends Readonly<Record<string, LauncherAgentIdentity>> {}
|
||||
|
||||
/**
|
||||
* Context key a launcher sets before any Loader entry mounts
|
||||
* (`ctx.provide(CONFIGURED_AGENT_IDENTITIES_KEY, identities)`) to fix
|
||||
* configured agents' session identities without a config key, so an overlay
|
||||
* repointing the row's model route cannot drop them.
|
||||
*/
|
||||
export const CONFIGURED_AGENT_IDENTITIES_KEY = 'configuredAgentIdentities'
|
||||
|
||||
/**
|
||||
* Apply launcher-owned identities over the configured agents, replacing both
|
||||
* identity keys for every entry the launcher named so a config-supplied
|
||||
* identity can never survive alongside a launcher-supplied one.
|
||||
* @param agents - the configured agent entries.
|
||||
* @param identities - launcher identities keyed by configured agent `id`, or `undefined`.
|
||||
* @returns the entries with launcher-owned identities applied.
|
||||
*/
|
||||
function applyLauncherIdentities(
|
||||
agents: Config['agents'],
|
||||
identities: ConfiguredAgentIdentities | undefined,
|
||||
): Config['agents'] {
|
||||
if (identities === undefined) return agents
|
||||
return agents.map((agent) => {
|
||||
const identity = identities[agent.id]
|
||||
if (identity === undefined) return agent
|
||||
const { sessionId: _sessionId, resumeSessionId: _resumeSessionId, ...rest } = agent
|
||||
return identity.resume
|
||||
? { ...rest, resumeSessionId: identity.id }
|
||||
: { ...rest, sessionId: identity.id }
|
||||
})
|
||||
}
|
||||
|
||||
/** Agent-loop plugin configuration. */
|
||||
export interface Config {
|
||||
/**
|
||||
@@ -220,6 +276,7 @@ export class AgentLoop extends Service implements AgentFactory {
|
||||
super(ctx, 'agentLoop')
|
||||
this.config = {
|
||||
...config,
|
||||
agents: applyLauncherIdentities(config.agents, ctx.get(CONFIGURED_AGENT_IDENTITIES_KEY)),
|
||||
maxParallelToolCalls: resolveMaxParallelToolCalls(config.maxParallelToolCalls),
|
||||
}
|
||||
validateConfiguredAgents(this.config.agents)
|
||||
|
||||
Reference in New Issue
Block a user