Merge remote-tracking branch 'origin/master' into xtr/agent-loop-message-machine
# Conflicts: # docs/architecture.i18n.yaml # docs/architecture.md # docs/architecture.zh.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/session.i18n.yaml # docs/event-producer-consumer.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl # examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl # packages/core/agent-loop/README.i18n.yaml # packages/core/agent-loop/README.md # packages/core/agent-loop/README.zh.md # packages/core/agent-loop/src/loop.ts # packages/core/agent/README.i18n.yaml # packages/core/agent/tests/llm-target.spec.ts # packages/core/session/tests/request-header.spec.ts
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
README.md: 7f78c20c46273ee1b3b59e6131ed5a9c3853f791
|
||||
README.zh.md: ab232cc161a554ea663025344cc758ec4b15e8ec
|
||||
# pnpm run verify-translation-pairing --write packages/core/agent/README.md
|
||||
README.md: a65c53b3e4edf2031f286d7d172e73357c66ff1e
|
||||
README.zh.md: 05da8a0a0d3ad2ed879b72e20eae1c4efaf711c8
|
||||
|
||||
@@ -12,7 +12,7 @@ Tracks live agents and carries the initiating Agent through asynchronous driver
|
||||
|
||||
### Public API
|
||||
|
||||
The scoped-registration surface: `Agent.ctx` is the agent's scope context (`dsh-scope`, key = the agent) — register tools/sections/variables/listeners through it for that agent alone, all unwound on disposal. `agentEvents(ctx, agent)` is the fused dispatcher for ordinary agent-subject operations (carrier + injected subject in one move); its notification mode invokes every listener and contains both synchronous throws and returned-promise rejections. The registry lifecycle pair reuses one stable routing carrier. `assembleContextFor(agent)` builds the per-agent assembly context (`agent` + `scope` together). `installAgentLlmTarget(agentCtx, target)` snapshots a mutable provider/model selection during prompt assembly and applies that pair to both prompt variables and request routing for one step. `CreateAgentOptions.setup(agentCtx)` and `ResumeAgentOptions.setup(agentCtx)` compose a fresh or resumed agent's scoped world while both objects remain unpublished. Setup is trusted, composition-only same-process code: drive the agent only after creation resolves.
|
||||
The scoped-registration surface: `Agent.ctx` is the agent's scope context (`dsh-scope`, key = the agent) — register tools/sections/variables/listeners through it for that agent alone, all unwound on disposal. `agentEvents(ctx, agent)` is the fused dispatcher for ordinary agent-subject operations (carrier + injected subject in one move); its notification mode invokes every listener and contains both synchronous throws and returned-promise rejections. The registry lifecycle pair reuses one stable routing carrier. `assembleContextFor(agent)` builds the per-agent assembly context (`agent` + `scope` together). `installAgentLlmTarget(agentCtx, target)` snapshots a mutable provider/model/reasoning-effort selection during prompt assembly, applies the route to prompt variables, and applies the complete target to request routing for one step; an absent selected effort clears an inherited effort so the target uses adapter/provider defaults. `CreateAgentOptions.setup(agentCtx)` and `ResumeAgentOptions.setup(agentCtx)` compose a fresh or resumed agent's scoped world while both objects remain unpublished. Setup is trusted, composition-only same-process code: drive the agent only after creation resolves.
|
||||
|
||||
- `ctx.agents.register(agent: Agent): () => void` — record an **already-constructed** agent. Disposed with the calling fiber.
|
||||
- Advanced ordered lifecycle: `enter(agent, owner): () => void` enforces `agent.id === agent.session.id`, performs the authoritative ID collision check, and inserts without announcing; `owner` explicitly records the live creator-agent relation (or `undefined` for a root), independently of durable session lineage. `announce(agent)` emits `agent/created` exactly once. A detach requested synchronously by a creation listener is deferred until that dispatch unwinds, and every detach checks the captured entry object, so a stale capability cannot delete a later same-ID replacement. The async factory uses this split; ordinary plugins use `register()`.
|
||||
|
||||
@@ -12,7 +12,7 @@ Agent 接口、注册表、进程本地发起方作用域,以及 `agent/*` 事
|
||||
|
||||
### 公开 API
|
||||
|
||||
带作用域的注册表层:`Agent.ctx` 是 agent 的作用域上下文(`dsh-scope`,键 = 该 agent)。通过它注册工具/段/变量/监听器,只对该 agent 生效,并在释放时全部撤销。`agentEvents(ctx, agent)` 是普通 agent 主体操作的融合分发器(一次完成载体 + 注入主体);其通知 mode 会调用每个监听器,并同时收容同步抛出和返回 Promise 的拒绝。注册表生命周期对复用一个稳定路由载体。`assembleContextFor(agent)` 构建按 agent 的组装上下文(同时包含 `agent` + `scope`)。`installAgentLlmTarget(agentCtx, target)` 在提示词组装期间快照可变的提供方/模型选择,并将该对同时应用到一个步骤的提示词变量与请求路由。`CreateAgentOptions.setup(agentCtx)` 和 `ResumeAgentOptions.setup(agentCtx)` 在新建或恢复的 agent 尚未发布时,组合其带作用域的世界。Setup 是受信任、仅用于组合的同进程代码:只有创建完成后才能驱动 agent。
|
||||
带作用域的注册表层:`Agent.ctx` 是 agent 的作用域上下文(`dsh-scope`,键 = 该 agent)。通过它注册工具/段/变量/监听器,只对该 agent 生效,并在释放时全部撤销。`agentEvents(ctx, agent)` 是普通 agent 主体操作的融合分发器(一次完成载体 + 注入主体);其通知 mode 会调用每个监听器,并同时收容同步抛出和返回 Promise 的拒绝。注册表生命周期对复用一个稳定路由载体。`assembleContextFor(agent)` 构建按 agent 的组装上下文(同时包含 `agent` + `scope`)。`installAgentLlmTarget(agentCtx, target)` 在提示词组装期间快照可变的提供方/模型/推理(reasoning)强度选择,将路由应用到提示词变量,并将完整目标应用到一个步骤的请求路由;如果没有选定推理强度,则会清除继承的推理强度,使该目标使用适配器/提供方默认值。`CreateAgentOptions.setup(agentCtx)` 和 `ResumeAgentOptions.setup(agentCtx)` 在新建或恢复的 agent 尚未发布时,组合其带作用域的世界。Setup 是受信任、仅用于组合的同进程代码:只有创建完成后才能驱动 agent。
|
||||
|
||||
- `ctx.agents.register(agent: Agent): () => void`:记录一个 **已经构造完成** 的 agent。随调用 fiber 释放。
|
||||
- 高级有序生命周期:`enter(agent, owner): () => void` 强制 `agent.id === agent.session.id`,执行权威 ID 冲突检查,并在不通知的情况下插入;`owner` 显式记录实时创建方 agent 关系(根 agent 为 `undefined`),与持久会话谱系无关。`announce(agent)` 恰好发出一次 `agent/created`。创建监听器同步请求的 detach 会延后到该次分发结束;每次 detach 都会检查捕获的条目对象,因此陈旧能力无法删除后续使用同一 ID 的替代项。异步工厂使用这一拆分;普通插件使用 `register()`。
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
/**
|
||||
* Agent-scoped provider/model target snapshot shared by interactive front doors.
|
||||
* Agent-scoped LLM target snapshot shared by interactive front doors.
|
||||
* @module @deepseek-ai/dsh-agent/llm-target
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import type { LlmCallConfig } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmCallConfig, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
/** Complete provider/model route selected for one live agent. */
|
||||
/** Complete provider/model route and optional reasoning effort selected for one live agent. */
|
||||
export interface AgentLlmTarget {
|
||||
/** Registered provider route. */
|
||||
provider: string
|
||||
/** Provider-owned model id. */
|
||||
model: string
|
||||
/** Adapter-owned reasoning effort, or provider/default behavior when absent. */
|
||||
reasoningEffort?: ReasoningEffortId
|
||||
}
|
||||
|
||||
/** Mutable selection plus the target captured for the current step. */
|
||||
@@ -24,9 +26,11 @@ export interface AgentLlmTargetRef {
|
||||
|
||||
/**
|
||||
* Couple one mutable target to agent-scoped prompt assembly and request routing.
|
||||
* Prompt assembly snapshots the selected pair before delegating, then applies
|
||||
* both prompt variables and request config to that snapshot so a concurrent
|
||||
* switch takes effect on a later step instead of splitting the two surfaces.
|
||||
* Prompt assembly snapshots the selected target before delegating, then applies
|
||||
* its route to prompt variables and its route/effort to request config so a
|
||||
* concurrent switch takes effect on a later step instead of splitting the two
|
||||
* surfaces. An absent selected effort clears any inherited effort so a model
|
||||
* switch can restore that target's provider/default behavior.
|
||||
*
|
||||
* @param agentCtx - The target agent's scoped context.
|
||||
* @param target - Mutable selection owned by the calling front door.
|
||||
@@ -52,10 +56,15 @@ export function installAgentLlmTarget(agentCtx: Context, target: AgentLlmTargetR
|
||||
async (_agent, _turn, _step, _signal, next): Promise<LlmCallConfig> => {
|
||||
const resolved = await next()
|
||||
const selected = target.assembled
|
||||
return selected === undefined ? resolved : {
|
||||
...resolved,
|
||||
if (selected === undefined) return resolved
|
||||
const { reasoningEffort: _inheritedEffort, ...withoutInheritedEffort } = resolved
|
||||
return {
|
||||
...withoutInheritedEffort,
|
||||
provider: selected.provider,
|
||||
model: selected.model,
|
||||
...selected.reasoningEffort === undefined
|
||||
? {}
|
||||
: { reasoningEffort: selected.reasoningEffort },
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type Agent,
|
||||
type AgentLlmTargetRef,
|
||||
} from '../src/index.ts'
|
||||
import type { LlmCallConfig } from '@deepseek-ai/dsh-llm'
|
||||
import { ReasoningEffortId, type LlmCallConfig } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
describe('installAgentLlmTarget()', () => {
|
||||
it('snapshots prompt variables and request routing together, then disposes both listeners', async () => {
|
||||
@@ -24,16 +24,31 @@ describe('installAgentLlmTarget()', () => {
|
||||
'agent/request', 1, 0, signal, () => Promise.resolve(seed),
|
||||
)).resolves.toBe(seed)
|
||||
|
||||
target.current = { provider: 'alpha', model: 'a1' }
|
||||
target.current = {
|
||||
provider: 'alpha',
|
||||
model: 'a1',
|
||||
reasoningEffort: ReasoningEffortId('high'),
|
||||
}
|
||||
expect((await ctx.systemPrompt.assemble()).variables).toMatchObject({ provider: 'alpha', model: 'a1' })
|
||||
target.current = { provider: 'beta', model: 'b1' }
|
||||
await expect(agentEvents(ctx, agent).waterfall(
|
||||
'agent/request', 1, 0, signal, () => Promise.resolve(seed),
|
||||
)).resolves.toEqual({ provider: 'alpha', model: 'a1', temperature: 0.2 })
|
||||
)).resolves.toEqual({
|
||||
provider: 'alpha',
|
||||
model: 'a1',
|
||||
reasoningEffort: ReasoningEffortId('high'),
|
||||
temperature: 0.2,
|
||||
})
|
||||
|
||||
expect((await ctx.systemPrompt.assemble()).variables).toMatchObject({ provider: 'beta', model: 'b1' })
|
||||
const inherited: LlmCallConfig = {
|
||||
provider: 'alpha',
|
||||
model: 'a1',
|
||||
reasoningEffort: ReasoningEffortId('max'),
|
||||
temperature: 0.2,
|
||||
}
|
||||
await expect(agentEvents(ctx, agent).waterfall(
|
||||
'agent/request', 1, 1, signal, () => Promise.resolve(seed),
|
||||
'agent/request', 1, 1, signal, () => Promise.resolve(inherited),
|
||||
)).resolves.toEqual({ provider: 'beta', model: 'b1', temperature: 0.2 })
|
||||
|
||||
dispose()
|
||||
|
||||
Reference in New Issue
Block a user