Merge remote-tracking branch 'origin/master' into xtr/identified-immutable-messages
# Conflicts: # docs/cordis-catalog/events.md # docs/core-data-structures/core.i18n.yaml # docs/event-producer-consumer.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/core/agent-loop/README.i18n.yaml # packages/core/agent/README.i18n.yaml
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# 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 packages/core/agent-loop/README.md
|
||||
README.md: 99fdc73e2dcb09ed7d47f021598dea8720cada5d
|
||||
README.zh.md: fe9db9a625ea289e7e9364a182061dee1a0e773c
|
||||
README.md: 6bb8b12af69f54c2a75cd672e4d3802887808c76
|
||||
README.zh.md: f9eb8aa3cdead427a88492e35c00eab80ba12f91
|
||||
|
||||
@@ -42,13 +42,14 @@ interface Config {
|
||||
id: string // required
|
||||
provider?: string
|
||||
model?: string
|
||||
maxTokens?: number // positive per-request output-token cap
|
||||
resumeSessionId?: string // load this persisted session instead of creating one
|
||||
cwd?: string // optional workspace cwd for the fresh session
|
||||
}>
|
||||
}
|
||||
```
|
||||
|
||||
Configured agents start automatically. A model call requires both `provider` and `model`; `agent/request` may supply a missing pair before dispatch. `maxParallelToolCalls` bounds every agent's rolling pool for parallel-safe calls and defaults to `10`. `cwd` applies only to fresh sessions, while `resumeSessionId` retains persisted metadata. Configured agents use the deployment persona, and programmatic setup can shadow it per agent. This plugin supplies the per-agent `provider`, `model`, and `cwd` prompt variables; harness identity and deployment persona belong to `dsh-system-prompt`.
|
||||
Configured agents start automatically. A model call requires both `provider` and `model`; `agent/request` may supply a missing pair before dispatch. An optional positive `maxTokens` seeds each conversation request's output cap and is logged in its request header. `maxParallelToolCalls` bounds every agent's rolling pool for parallel-safe calls and defaults to `10`. `cwd` applies only to fresh sessions, while `resumeSessionId` retains persisted metadata. Configured agents use the deployment persona, and programmatic setup can shadow it per agent. This plugin supplies the per-agent `provider`, `model`, and `cwd` prompt variables; harness identity and deployment persona belong to `dsh-system-prompt`.
|
||||
|
||||
### Internal concrete driver
|
||||
|
||||
|
||||
@@ -42,13 +42,14 @@ interface Config {
|
||||
id: string // required
|
||||
provider?: string
|
||||
model?: string
|
||||
maxTokens?: number // positive per-request output-token cap
|
||||
resumeSessionId?: string // load this persisted session instead of creating one
|
||||
cwd?: string // optional workspace cwd for the fresh session
|
||||
}>
|
||||
}
|
||||
```
|
||||
|
||||
通过配置创建的 agent 会自动启动。模型调用同时需要 `provider` 和 `model`;`agent/request` 可以在分发前补齐缺失的这一对值。`maxParallelToolCalls` 限制每个 agent 针对并行安全调用使用的滚动池,默认值为 `10`。`cwd` 仅应用于全新会话,而 `resumeSessionId` 保留持久化元数据。通过配置创建的 agent 使用部署 persona;编程式 setup 可以按 agent 遮蔽它。该插件提供逐 agent 的 `provider`、`model` 和 `cwd` 提示词变量;harness 身份与部署 persona 属于 `dsh-system-prompt`。
|
||||
通过配置创建的 agent 会自动启动。模型调用同时需要 `provider` 和 `model`;`agent/request` 可以在分发前补齐缺失的这一对值。可选的正整数 `maxTokens` 会为每次对话请求提供初始输出上限,并记录在请求 header 中。`maxParallelToolCalls` 限制每个 agent 针对并行安全调用使用的滚动池,默认值为 `10`。`cwd` 仅应用于全新会话,而 `resumeSessionId` 保留持久化元数据。通过配置创建的 agent 使用部署 persona;编程式 setup 可以按 agent 遮蔽它。该插件提供逐 agent 的 `provider`、`model` 和 `cwd` 提示词变量;harness 身份与部署 persona 属于 `dsh-system-prompt`。
|
||||
|
||||
### 包内部实体驱动器
|
||||
|
||||
|
||||
@@ -581,11 +581,16 @@ export class ReactLoopAgent implements Agent {
|
||||
&& persistedConfig.model === route.model
|
||||
? persistedConfig.reasoningEffort
|
||||
: undefined
|
||||
const maxTokens = this.options.maxTokens
|
||||
const seedConfig = deepFreeze(structuredClone(
|
||||
this.requestHeaderLogged
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- the instance logged the header it now folds
|
||||
? persistedConfig!
|
||||
: { ...route, ...reasoningEffort === undefined ? {} : { reasoningEffort } },
|
||||
: {
|
||||
...route,
|
||||
...reasoningEffort === undefined ? {} : { reasoningEffort },
|
||||
...maxTokens === undefined ? {} : { maxTokens },
|
||||
},
|
||||
))
|
||||
const proposedConfig = await this.loopCtx.waterfall(
|
||||
agentCarrier(this), 'agent/request', this, turn, step, signal,
|
||||
|
||||
@@ -112,6 +112,14 @@ function resolveMaxParallelToolCalls(value: number | undefined): number {
|
||||
return maxParallelToolCalls
|
||||
}
|
||||
|
||||
/** Reject an output-token cap that cannot be represented exactly on the request wire. */
|
||||
function assertAgentOptions(options: AgentOptions): void {
|
||||
if (options.maxTokens !== undefined
|
||||
&& (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0)) {
|
||||
throw new TypeError('agent maxTokens must be a positive safe integer')
|
||||
}
|
||||
}
|
||||
|
||||
/** Prepared-but-unpublished agent resources sharing one memoized teardown. */
|
||||
interface PreparedAgent {
|
||||
agent: ReactLoopAgent
|
||||
@@ -196,6 +204,7 @@ export class AgentLoop extends Service implements AgentFactory {
|
||||
sessionId: z.string().min(1),
|
||||
provider: z.string(),
|
||||
model: z.string(),
|
||||
maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER),
|
||||
cwd: z.string(),
|
||||
resumeSessionId: z.string(),
|
||||
})).default([]),
|
||||
@@ -327,6 +336,7 @@ export class AgentLoop extends Service implements AgentFactory {
|
||||
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
||||
*/
|
||||
private prepare(ownerCtx: Context, id: SessionId, options: AgentOptions, session: Session, callerSignal?: AbortSignal): PreparedAgent {
|
||||
assertAgentOptions(options)
|
||||
ownerCtx.fiber.assertActive()
|
||||
// Every caller reaches prepare() synchronously from a service method
|
||||
// whose Cordis dispatch already requires the live factory fiber, or
|
||||
|
||||
@@ -46,6 +46,19 @@ function send(agent: Agent, text: string) {
|
||||
}
|
||||
|
||||
describe('agent loop', () => {
|
||||
it.each([0, -1, 1.5, Number.NaN, Number.MAX_SAFE_INTEGER + 1])(
|
||||
'rejects invalid AgentOptions.maxTokens %s before publication',
|
||||
async (maxTokens) => {
|
||||
const ctx = await harness(new MockAdapter([]))
|
||||
expect(() => ctx.agentLoop.create(
|
||||
SessionId('invalid-max-tokens'),
|
||||
{ provider: 'mock', model: 'mock', maxTokens },
|
||||
)).toThrow('agent maxTokens must be a positive safe integer')
|
||||
expect(ctx.agents.list()).toEqual([])
|
||||
expect(ctx.sessions.list()).toEqual([])
|
||||
},
|
||||
)
|
||||
|
||||
it('runs a simple turn: queued message → model → idle, with ordered events', async () => {
|
||||
const adapter = new MockAdapter([textResponse('hello there')])
|
||||
const ctx = await harness(adapter)
|
||||
|
||||
Reference in New Issue
Block a user