Merge finalized Web transcript parent

This commit is contained in:
Hypatia May
2026-07-31 09:46:32 +08:00
744 changed files with 19212 additions and 3369 deletions

View File

@@ -67,6 +67,7 @@ export class MockAdapter extends LlmAdapter {
constructor(
private script: (StreamChunk[] | ((options: GenerateOptions) => StreamChunk[]) | 'hang')[],
private readonly reasoning?: LlmModelReasoningInfo,
private readonly defaultMaxTokens?: number,
) {
super()
}
@@ -80,6 +81,7 @@ export class MockAdapter extends LlmAdapter {
id: model,
name: model,
...this.reasoning === undefined ? {} : { reasoning: this.reasoning },
...this.defaultMaxTokens === undefined ? {} : { defaultMaxTokens: this.defaultMaxTokens },
})
}

View File

@@ -71,7 +71,7 @@ function waitForIdle(context: Context, agent: Agent): Promise<void> {
describe.skipIf(!process.env.DEEPSEEK_API_KEY)('log-derived request cache hits (real API)', () => {
it('every request after the first hits the provider prefix cache', async () => {
ctx = await loopHarness()
const agent = ctx.agentLoop.create(SessionId('cache-e2e'), { provider: 'deepseek', model: 'deepseek-v4-flash' })
const agent = ctx.agentLoop.create(SessionId('cache-e2e'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' })
// Turn 1: forces a tool call → at least two steps (two model requests).
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'Look up the key "deploy-color" with the lookup tool and tell me the value.' }], source: { kind: 'user' } }))

View File

@@ -18,6 +18,13 @@ import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { MockAdapter, textResponse, toolCallResponse } from './mock-adapter.ts'
async function harness(adapter: MockAdapter, persona = 'stable base') {
return harnessRoutes([['mock', adapter]], persona)
}
async function harnessRoutes(
adapters: readonly (readonly [provider: string, adapter: MockAdapter])[],
persona = 'stable base',
) {
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(SessionStore)
@@ -25,7 +32,7 @@ async function harness(adapter: MockAdapter, persona = 'stable base') {
await ctx.plugin(ToolRegistry)
await ctx.plugin(AgentRegistry)
await ctx.plugin(AgentLoop, { agents: [] })
ctx.llm.registerAdapter(['mock'], adapter)
for (const [provider, adapter] of adapters) ctx.llm.registerAdapter([provider], adapter)
return ctx
}
@@ -134,6 +141,10 @@ describe('request stability across the loop', () => {
ReasoningEffortId('high'),
ReasoningEffortId('max'),
])
expect(headers.map(event => event.data.header.adapterDefaults)).toEqual([
{ reasoningEffort: true },
undefined,
])
expect(headers.map(event => event.data.reason)).toEqual(['initial', 'change'])
for (const [model, effort] of [
@@ -158,6 +169,88 @@ describe('request stability across the loop', () => {
}
})
it('logs an adapter-owned maxTokens default before dispatch', async () => {
const adapter = new MockAdapter([textResponse('bounded')], undefined, 256_000)
const ctx = await harness(adapter)
const agent = ctx.agentLoop.create(SessionId('adapter-max-tokens'), {
provider: 'mock',
model: 'mock',
})
send(agent, 'use the adapter output limit')
await waitForIdle(ctx, agent)
expect(adapter.requests[0]?.maxTokens).toBe(256_000)
const header = agent.session.events.find(event => event.type === 'request/header')
expect(header?.type === 'request/header' && header.data.header.config.maxTokens).toBe(256_000)
expect(header?.type === 'request/header' && header.data.header.adapterDefaults)
.toEqual({ maxTokens: true })
})
it('rematerializes the selected adapter maxTokens default after a provider switch', async () => {
const deepseek = new MockAdapter([textResponse('deepseek')], undefined, 256_000)
const other = new MockAdapter([textResponse('other')], undefined, 8_192)
const ctx = await harnessRoutes([
['deepseek', deepseek],
['other', other],
])
const agent = ctx.agentLoop.create(SessionId('adapter-max-tokens-switch'), {
provider: 'deepseek',
model: 'deepseek-model',
})
ctx.on('agent/request', async (_agent, turn, _step, _signal, next) => {
const config = await next()
return turn === 2
? { ...config, provider: 'other', model: 'other-model' }
: config
})
send(agent, 'first')
await waitForIdle(ctx, agent)
send(agent, 'second')
await waitForIdle(ctx, agent)
expect(deepseek.requests[0]?.maxTokens).toBe(256_000)
expect(other.requests[0]?.maxTokens).toBe(8_192)
const headers = agent.session.events.filter(event => event.type === 'request/header')
expect(headers.map(event => event.data.header.config.maxTokens)).toEqual([256_000, 8_192])
expect(headers.map(event => event.data.header.adapterDefaults)).toEqual([
{ maxTokens: true },
{ maxTokens: true },
])
})
it('preserves an explicit agent maxTokens cap across a provider switch', async () => {
const deepseek = new MockAdapter([textResponse('deepseek')], undefined, 256_000)
const other = new MockAdapter([textResponse('other')], undefined, 8_192)
const ctx = await harnessRoutes([
['deepseek', deepseek],
['other', other],
])
const agent = ctx.agentLoop.create(SessionId('explicit-max-tokens-switch'), {
provider: 'deepseek',
model: 'deepseek-model',
maxTokens: 4_096,
})
ctx.on('agent/request', async (_agent, turn, _step, _signal, next) => {
const config = await next()
return turn === 2
? { ...config, provider: 'other', model: 'other-model' }
: config
})
send(agent, 'first')
await waitForIdle(ctx, agent)
send(agent, 'second')
await waitForIdle(ctx, agent)
expect(deepseek.requests[0]?.maxTokens).toBe(4_096)
expect(other.requests[0]?.maxTokens).toBe(4_096)
const headers = agent.session.events.filter(event => event.type === 'request/header')
expect(headers.map(event => event.data.header.config.maxTokens)).toEqual([4_096, 4_096])
expect(headers.map(event => event.data.header.adapterDefaults)).toEqual([undefined, undefined])
})
it('keeps exact-model resolution, request logging, and dispatch on one adapter registration', async () => {
const ctx = new Context()
await ctx.plugin(LlmService)