cleanup(agent-loop): simplify request construction

This commit is contained in:
_Kerman
2026-07-27 17:12:28 +08:00
parent 749927ecd8
commit 1ffaa5bb10
2 changed files with 30 additions and 37 deletions

View File

@@ -535,25 +535,19 @@ export class ReactLoopAgent implements Agent {
): Promise<{ request: GenerateOptions; preparedCall?: PreparedLlmCall }> {
const { session } = this
const loggedConfig = session.requestHeader()?.config
const initialProvider = this.options.provider ?? ''
const initialModel = this.options.model ?? ''
const initialConfig: LlmCallConfig = {
provider: initialProvider,
model: initialModel,
...loggedConfig?.provider === initialProvider
&& loggedConfig.model === initialModel
&& loggedConfig.reasoningEffort !== undefined
? { reasoningEffort: loggedConfig.reasoningEffort }
: {},
}
// A loop instance starts from its declared route, restoring only an opaque
// effort owned by that exact model. Later steps fold the config it logged.
const seedConfig: LlmCallConfig = deepFreeze(structuredClone(
const persistedConfig = session.requestHeader()?.config
const route = { provider: this.options.provider ?? '', model: this.options.model ?? '' }
const reasoningEffort = persistedConfig?.provider === route.provider
&& persistedConfig.model === route.model
? persistedConfig.reasoningEffort
: undefined
const seedConfig = deepFreeze(structuredClone(
this.requestHeaderLogged
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- a logged instance anchor guarantees the fold
? session.requestHeader()!.config
: initialConfig,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- the instance logged the header it now folds
? persistedConfig!
: { ...route, ...reasoningEffort === undefined ? {} : { reasoningEffort } },
))
const proposedConfig = await this.loopCtx.waterfall(
agentCarrier(this), 'agent/request', this, turn, step, signal,
@@ -590,17 +584,10 @@ export class ReactLoopAgent implements Agent {
}
const request = markAgentLoopRequest(deepFreeze({
provider: header.config.provider,
model: header.config.model,
...header.config.reasoningEffort !== undefined
? { reasoningEffort: header.config.reasoningEffort }
: {},
...header.config,
messages: boundaryMessages,
...header.system !== undefined ? { system: header.system } : {},
...header.tools !== undefined ? { tools: header.tools } : {},
...header.config.temperature !== undefined ? { temperature: header.config.temperature } : {},
...header.config.maxTokens !== undefined ? { maxTokens: header.config.maxTokens } : {},
...header.config.stop !== undefined ? { stop: header.config.stop } : {},
sessionId: session.id,
signal,
}))

View File

@@ -136,20 +136,26 @@ describe('request stability across the loop', () => {
])
expect(headers.map(event => event.data.reason)).toEqual(['initial', 'change'])
const resumedAdapter = new MockAdapter([textResponse('three')], reasoning)
const resumedCtx = await harness(resumedAdapter)
const resumedHandle = await resumedCtx.agents.create({
sessionId: SessionId('effort-resumed'),
seed: structuredClone(agent.session.events),
agentOptions: { provider: 'mock', model: 'mock' },
})
send(resumedHandle.agent, 'third')
await waitForIdle(resumedCtx, resumedHandle.agent)
for (const [model, effort] of [
['mock', ReasoningEffortId('max')],
['replacement', ReasoningEffortId('high')],
] as const) {
const resumedAdapter = new MockAdapter([textResponse('resumed')], reasoning)
const resumedCtx = await harness(resumedAdapter)
const resumedHandle = await resumedCtx.agents.create({
sessionId: SessionId(`effort-${model}`),
seed: structuredClone(agent.session.events),
agentOptions: { provider: 'mock', model },
})
send(resumedHandle.agent, 'resumed')
await waitForIdle(resumedCtx, resumedHandle.agent)
expect(resumedAdapter.requests[0]?.reasoningEffort).toBe(ReasoningEffortId('max'))
const resumedHeaders = resumedHandle.agent.session.events.filter(event => event.type === 'request/header')
expect(resumedHeaders.at(-1)?.data.header.config.reasoningEffort).toBe(ReasoningEffortId('max'))
expect(resumedHeaders.at(-1)?.data.reason).toBe('resume')
expect(resumedAdapter.requests[0]?.model).toBe(model)
expect(resumedAdapter.requests[0]?.reasoningEffort).toBe(effort)
const resumedHeaders = resumedHandle.agent.session.events.filter(event => event.type === 'request/header')
expect(resumedHeaders.at(-1)?.data.header.config.reasoningEffort).toBe(effort)
expect(resumedHeaders.at(-1)?.data.reason).toBe('resume')
}
})
it('keeps exact-model resolution, request logging, and dispatch on one adapter registration', async () => {