From 418dd6ef357f3125463db1720eea63ff1b24c30c Mon Sep 17 00:00:00 2001 From: ztygod <2739022972@qq.com> Date: Sun, 19 Jul 2026 20:07:51 +0800 Subject: [PATCH 1/2] refactor(context): decouple ContextManager dependencies --- .pnpm-store/v11/index.db-shm | Bin 32768 -> 0 bytes .pnpm-store/v11/index.db-wal | 0 src/agent/runtime/context-manager.ts | 71 +++++++++++++++++++++------ 3 files changed, 55 insertions(+), 16 deletions(-) delete mode 100644 .pnpm-store/v11/index.db-shm delete mode 100644 .pnpm-store/v11/index.db-wal diff --git a/.pnpm-store/v11/index.db-shm b/.pnpm-store/v11/index.db-shm deleted file mode 100644 index fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuAr62r3(); /** Creates a context manager with static agent-level context providers. */ @@ -65,6 +74,36 @@ export class ContextManager { memory: options.memory, contextProviders: options.contextProviders, }); + + this.tokenEstimator = options.tokenEstimator ?? createDefaultTokenEstimator(); + + this.transcriptProjector = options.transcriptProjector ?? new TranscriptProjector(); + + this.budgeter = + options.budgeter ?? + new ContextBudgeter({ + tokenEstimator: this.tokenEstimator, + }); + + this.compressionPipeline = + options.compressionPipeline ?? + new ContextCompressionPipeline({ + compressor: new RuleBasedContextCompressor({ + tokenEstimator: this.tokenEstimator, + }), + + tokenEstimator: this.tokenEstimator, + }); + + this.truncator = + options.truncator ?? + new ContextTruncator({ + tokenEstimator: this.tokenEstimator, + }); + + this.promptAssembler = options.promptAssembler ?? new PromptAssembler(); + + this.systemPromptService = options.systemPromptService ?? new SystemPromptService(); } /** Initializes static context sources and the mutable transcript for a run. */ From 395c20d50afc4de17b762c3485fb82a787273c07 Mon Sep 17 00:00:00 2001 From: ztygod <2739022972@qq.com> Date: Sun, 19 Jul 2026 20:53:59 +0800 Subject: [PATCH 2/2] refactor(context): extract context provider executor --- src/agent/runtime/context-manager.ts | 2 +- src/agent/types.ts | 11 +- src/context/collector/context-collector.ts | 161 ++++++++++++++---- .../collector/context-provider-executor.ts | 105 ++++++++++++ 4 files changed, 247 insertions(+), 32 deletions(-) create mode 100644 src/context/collector/context-provider-executor.ts diff --git a/src/agent/runtime/context-manager.ts b/src/agent/runtime/context-manager.ts index 0f3e105..158dbde 100644 --- a/src/agent/runtime/context-manager.ts +++ b/src/agent/runtime/context-manager.ts @@ -39,7 +39,7 @@ export type ContextManagerOptions = { contextProviders?: readonly AgentContextProvider[]; /** - * Context build dependencies. + * Context build core dependencies. */ transcriptProjector?: TranscriptProjector; diff --git a/src/agent/types.ts b/src/agent/types.ts index e0292ca..7a7879c 100644 --- a/src/agent/types.ts +++ b/src/agent/types.ts @@ -75,8 +75,15 @@ export type AgentContextValue = /** Deferred context provider used to attach dynamic runtime context. */ export type AgentContextProvider = { name: string; - description?: string; - build(context: AgentRunContext): AgentContextValue | Promise; + critical?: boolean; + timeoutMs?: number; + + build( + context: AgentRunContext, + options: { + signal: AbortSignal; + }, + ): Promise; }; /** User-facing input for one agent run. */ diff --git a/src/context/collector/context-collector.ts b/src/context/collector/context-collector.ts index 13cffb4..aa19726 100644 --- a/src/context/collector/context-collector.ts +++ b/src/context/collector/context-collector.ts @@ -2,9 +2,16 @@ import type {AgentContextProvider, AgentContextValue} from "../../agent/types.js import type {AgentMemory} from "../../agent/runtime/memory.js"; import type {AgentRunState} from "../../agent/runtime/run-state.js"; import type {ContextDocument, ContextSection} from "../types.js"; +import {ContextProviderExecutor} from "./context-provider-executor.js"; export type ContextCollectorOptions = { memory: AgentMemory; + + /** + * Static context providers. + * + * These providers are registered when creating Agent runtime. + */ contextProviders?: readonly AgentContextProvider[]; }; @@ -12,46 +19,92 @@ export type CollectContextOptions = { stage?: ContextDocument["metadata"]["stage"]; }; -/** Collects run context sources without applying ordering or budget policy. */ +/** + * Collects context sources and builds ContextDocument. + * + * Responsibilities: + * - load memory + * - collect workspace/user context + * - execute context providers + * - convert values into ContextSection + * + * Not responsible for: + * - token budgeting + * - compression + * - ordering policy + * - prompt assembling + */ export class ContextCollector { - private readonly contextProviders: AgentContextProvider[]; + private readonly contextProviders: readonly AgentContextProvider[]; + + private readonly providerExecutor: ContextProviderExecutor; constructor(private readonly options: ContextCollectorOptions) { - this.contextProviders = [...(options.contextProviders ?? [])]; + this.contextProviders = options.contextProviders ?? []; + + this.providerExecutor = new ContextProviderExecutor(); } async collect( run: AgentRunState, options: CollectContextOptions = {}, ): Promise { - const sections = [ - ...(await this.loadMemory(run)).map((value) => toContextSection(value, "memory")), + const sections: ContextSection[] = []; + + /** + * Memory Context + */ + const memories = await this.loadMemory(run); + + sections.push(...memories.map((value) => toContextSection(value, "memory"))); + + /** + * User provided context + */ + sections.push( ...(run.input.context ?? []).map((value) => toContextSection(value, "user")), - { - title: "Workspace Profile", - priority: 100, - content: JSON.stringify(run.workspaceProfile, null, 2), - source: {kind: "workspace"}, - } satisfies ContextSection, - ]; + ); + + /** + * Workspace Context + */ + sections.push({ + title: "Workspace Profile", + + priority: 100, + content: JSON.stringify(run.workspaceProfile, null, 2), + + source: { + kind: "workspace", + }, + }); + + /** + * Context Providers + */ const providers = [...this.contextProviders, ...(run.input.contextProviders ?? [])]; - for (const provider of providers) { - const value = await provider.build(run.context); - sections.push( - typeof value === "string" - ? { - title: provider.name, - content: value, - source: {kind: "provider", ref: provider.name}, - } - : { - title: value.title ?? provider.name, - ...value, - source: {kind: "provider", ref: provider.name}, - }, - ); + const providerResults = await this.providerExecutor.execute(providers, run.context); + + for (const result of providerResults) { + /** + * Non critical provider failure + * + * Executor already isolates errors. + * Collector only skips invalid results. + */ + if (!result.success) { + console.warn(`[ContextProvider] ${result.provider.name} failed:`, result.error); + + continue; + } + + if (result.value === undefined) { + continue; + } + + sections.push(toProviderSection(result.provider, result.value)); } return { @@ -68,17 +121,67 @@ export class ContextCollector { private async loadMemory(run: AgentRunState): Promise { const runMemory = await this.options.memory.loadRunMemory?.(run); const projectMemory = await this.options.memory.loadProjectMemory?.(run); + return [...(projectMemory ?? []), ...(runMemory ?? [])]; } } +/** + * Convert common context value to ContextSection. + */ function toContextSection( value: AgentContextValue, source: "memory" | "user", ): ContextSection { if (typeof value === "string") { - return {content: value, source: {kind: source}}; + return { + content: value, + + source: { + kind: source, + }, + }; + } + + return { + ...value, + + source: { + kind: source, + }, + }; +} + +/** + * Convert provider result to ContextSection. + */ +function toProviderSection( + provider: AgentContextProvider, + value: AgentContextValue, +): ContextSection { + if (typeof value === "string") { + return { + title: provider.name, + + content: value, + + source: { + kind: "provider", + + ref: provider.name, + }, + }; } - return {...value, source: {kind: source}}; + return { + title: value.title ?? provider.name, + + ...value, + + source: { + kind: "provider", + + ref: provider.name, + }, + }; } diff --git a/src/context/collector/context-provider-executor.ts b/src/context/collector/context-provider-executor.ts new file mode 100644 index 0000000..1ffcaff --- /dev/null +++ b/src/context/collector/context-provider-executor.ts @@ -0,0 +1,105 @@ +import type { + AgentContextProvider, + AgentContextValue, + AgentRunContext, +} from "../../agent/types.js"; + +export type ProviderExecutionResult = { + provider: AgentContextProvider; + value?: AgentContextValue; + error?: Error; + durationMs: number; + success: boolean; +}; + +/** + * Executes context providers with isolation. + * + * Responsibilities: + * - run providers concurrently + * - handle provider errors + * - apply timeout policy + * - collect execution metrics + * + * Not responsible for: + * - converting values to ContextSection + * - deciding critical provider behavior + */ +export class ContextProviderExecutor { + async execute( + providers: readonly AgentContextProvider[], + context: AgentRunContext, + ): Promise { + return Promise.all( + providers.map((provider) => this.executeProvider(provider, context)), + ); + } + + private async executeProvider( + provider: AgentContextProvider, + context: AgentRunContext, + ): Promise { + const start = Date.now(); + const controller = new AbortController(); + + try { + const value = await this.withTimeout( + provider.build(context, {signal: controller.signal}), + provider.timeoutMs, + provider.name, + ); + + return { + provider, + + value, + + success: true, + + durationMs: Date.now() - start, + }; + } catch (error) { + return { + provider, + + error: error instanceof Error ? error : new Error(String(error)), + + success: false, + + durationMs: Date.now() - start, + }; + } + } + + private async withTimeout( + promise: Promise, + timeoutMs?: number, + providerName?: string, + ): Promise { + /** + * Provider 没有配置 timeout + * 直接等待 + */ + if (!timeoutMs) { + return promise; + } + + return Promise.race([ + promise, + + new Promise((_, reject) => { + const timer = setTimeout(() => { + reject( + new Error(`Context provider "${providerName}" timeout after ${timeoutMs}ms`), + ); + }, timeoutMs); + + /** + * Node 环境: + * 防止 timer 阻止进程退出 + */ + timer.unref?.(); + }), + ]); + } +}