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:
_Kerman
2026-07-27 16:44:06 +08:00
220 changed files with 4592 additions and 969 deletions

View File

@@ -287,7 +287,7 @@ export class BasicCompactService extends CompactService {
return this.compactRegion(range.start, range.end, agent, signal)
}
const context = await this.ctx.llm.resolveModelContext(target.provider, target.model)
const context = (await this.ctx.llm.resolveModelInfo(target.provider, target.model, signal)).context
const targetKey = `${target.provider}/${target.model}`
if (context === undefined) {
throw new TargetPressureConfigError(

View File

@@ -16,7 +16,7 @@ import type {
ContentBlock,
GenerateOptions,
LlmFailure,
LlmModelContext,
LlmResolvedModelInfo,
Message,
StreamChunk,
} from '@deepseek-ai/dsh-llm'
@@ -33,8 +33,13 @@ class ContextAdapter extends LlmAdapter {
super()
}
override resolveModelContext(): Promise<LlmModelContext> {
return Promise.resolve({ contextWindow: this.contextWindow })
override resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {
return Promise.resolve({
provider,
id: model,
name: model,
context: { contextWindow: this.contextWindow },
})
}
override async * stream(): AsyncIterable<StreamChunk> {
@@ -47,9 +52,14 @@ class RoutedContextAdapter extends LlmAdapter {
super()
}
override resolveModelContext(provider: string): Promise<LlmModelContext | undefined> {
override resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {
const contextWindow = this.windows[provider]
return Promise.resolve(contextWindow === undefined ? undefined : { contextWindow })
return Promise.resolve({
provider,
id: model,
name: model,
...contextWindow === undefined ? {} : { context: { contextWindow } },
})
}
override async * stream(): AsyncIterable<StreamChunk> {
@@ -457,6 +467,18 @@ describe('pressure measurement and retention', () => {
.resolves.not.toBeNull()
})
it('forwards turn cancellation to proactive model metadata resolution', async () => {
const ctx = createContext()
const resolveModelInfo = vi.spyOn(ctx.llm, 'resolveModelInfo')
const compact = service(compactConfig, ctx)
const session = conversation()
const signal = new AbortController().signal
await expect(compact.compactIfNeeded(agent(session, MODEL), 'pressure', signal))
.resolves.not.toBeNull()
expect(resolveModelInfo).toHaveBeenCalledWith(MODEL, MODEL, signal)
})
it('re-resolves capacity after a same-model-id provider switch in one session', async () => {
const ctx = new Context()
void new LlmService(ctx)
@@ -489,7 +511,11 @@ describe('pressure measurement and retention', () => {
void new LlmService(ctx)
void new TokenMeterService(ctx)
ctx.llm.registerAdapter(['unknown-context'], new ContextAdapter(1_000))
vi.spyOn(ctx.llm, 'resolveModelContext').mockResolvedValue(undefined)
vi.spyOn(ctx.llm, 'resolveModelInfo').mockImplementation((provider, model) => Promise.resolve({
provider,
id: model,
name: model,
}))
const compact = service(compactConfig, ctx)
const session = conversation(4)
session.append('request/header', {
@@ -1333,7 +1359,11 @@ describe('automatic listener and loader composition', () => {
const ctx = createContext()
const warnings: string[] = []
ctx.logger.warn = ((message: string) => void warnings.push(message)) as typeof ctx.logger.warn
vi.spyOn(ctx.llm, 'resolveModelContext').mockResolvedValue(undefined)
vi.spyOn(ctx.llm, 'resolveModelInfo').mockImplementation((provider, model) => Promise.resolve({
provider,
id: model,
name: model,
}))
void new TestCompactService(ctx, {
thresholdRatio: 0.5,
retainTokens: 180,

View File

@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { toolPairingBalancedAfter, toolPairingBalancedBefore } from '@deepseek-ai/dsh-compact'
import { CONTEXT_WINDOW_EXCEEDED_CODE, LlmError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, GenerateOptions, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm'
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
import type { Agent } from '@deepseek-ai/dsh-agent'
@@ -41,8 +41,13 @@ class StepwiseToolAdapter extends LlmAdapter {
super()
}
override resolveModelContext(): Promise<{ contextWindow: number }> {
return Promise.resolve({ contextWindow: 400 })
override resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {
return Promise.resolve({
provider,
id: model,
name: model,
context: { contextWindow: 400 },
})
}
async * stream(_options: GenerateOptions): AsyncIterable<StreamChunk> {
@@ -76,8 +81,13 @@ class OverflowRecoveryAdapter extends LlmAdapter {
super()
}
override resolveModelContext(): Promise<{ contextWindow: number }> {
return Promise.resolve({ contextWindow: 128 })
override resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {
return Promise.resolve({
provider,
id: model,
name: model,
context: { contextWindow: 128 },
})
}
override async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {