Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
所有的测试文件只能写在现有的test文件夹下
修改过程中发现错误,如果是本次范围就修改,否则要在最后指出
当前的设计不能假设单会话的,而应该假设多会话场景
在用户明确要求执行修改代码时才能改代码,以最新的一条用户信息为准
在用户的最新的一条消息除非有显式命令(执行方案、修改代码等)要求修改代码,否则绝对不改代码,之前要求修改的指令全部不算数,别再根据之前的上下文或者当前不确定的指令猜是不是要直接修改代码了
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
所有的测试文件只能写在现有的test文件夹下
修改过程中发现错误,如果是本次范围就修改,否则要在最后指出
当前的设计不能假设单会话的,而应该假设多会话场景
在用户明确要求执行修改代码时才能改代码,以最新的一条用户信息为准
在用户的最新的一条消息除非有显式命令(执行方案、修改代码等)要求修改代码,否则绝对不改代码,之前要求修改的指令全部不算数,别再根据之前的上下文或者当前不确定的指令猜是不是要直接修改代码了
54 changes: 29 additions & 25 deletions packages/codingcode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { Message, ToolCall } from '../core/types.js';
import { AgentError } from '../core/error.js';
import { Result } from '../core/result.js';
import type { ToolDescription } from '../tools/types.js';
import type { LLMResponse } from '../llm/types.js';
import type { LLMClient } from '../llm/client.js';
import { ToolService } from '../tools/registry.js';
import { ToolExecutorService } from '../tools/executor.js';
import { ContextService } from '../context/context.js';
Expand All @@ -11,6 +13,7 @@ import { CheckpointService } from '../checkpoint/checkpoint-service.js';
import { buildSystemPrompt, type SystemPromptVariant } from './prompt.js';
import { resolveConfig } from './config.js';
import { getContextConfig } from '../context/config.js';
import { estimateTokens } from '../context/utils/tokens.js';
import { ToolSearchService } from '../tools/tool-search-service.js';
import { sharedTodoStore } from '../self/todo.js';
import { buildToolsForAgent, buildDeferredCatalogContent } from './build-tools.js';
Expand All @@ -26,7 +29,7 @@ export const sendMessage = (
sessionId: string | undefined,
input: string,
cwd: string,
llm: any,
llm: LLMClient,
options?: {
signal?: AbortSignal
},
Expand Down Expand Up @@ -66,15 +69,16 @@ export type AgentEvent =
| { readonly _tag: 'ApprovalRequest'; readonly id: string; readonly tool: string; readonly args: Record<string, unknown> }
| { readonly _tag: 'ToolResult'; readonly id: string; readonly name: string; readonly output: string; readonly ok: boolean }
| { readonly _tag: 'Step'; readonly step: number; readonly max: number }
| { readonly _tag: 'ReactiveCompact'; readonly attempt: number; readonly released: number }
| { readonly _tag: 'ReactiveCompact'; readonly attempt: number; readonly released: number; readonly promptEstimate: number }
| { readonly _tag: 'Error'; readonly error: AgentError }
| { readonly _tag: 'Done'; readonly content: string }
| { readonly _tag: 'TodoUpdate'; readonly items: ReadonlyArray<{ readonly step: string; readonly status: 'pending' | 'in_progress' | 'completed' }> }
| { readonly _tag: 'TurnId'; readonly turnId: number };
| { readonly _tag: 'TurnId'; readonly turnId: number }
| { readonly _tag: 'Usage'; readonly prompt: number; readonly completion: number; readonly total: number };

export interface RunStreamOptions {
state: SessionStoreState;
llm: LLMStreamAdapter;
llm: LLMClient;
skillInstruction?: string;
systemPromptVariant?: SystemPromptVariant;
systemOverride?: string;
Expand All @@ -87,19 +91,6 @@ export interface RunStreamOptions {
approvalOverride?: any;
}

export interface LLMStreamAdapter {
completeStream(params: {
messages: Message[];
system?: string;
tools?: ToolDescription[];
maxSteps?: number;
signal?: AbortSignal;
}): {
stream: AsyncIterable<string>;
response: Promise<Result<{ content: string; toolCalls?: ToolCall[] }, AgentError>>;
};
}

interface RunReActDeps {
maxSteps: number;
maxStopContinuations: number;
Expand Down Expand Up @@ -201,6 +192,18 @@ export async function* runReActLoop(
const stepBeforePayload = { sessionId, step: step + 1 };
await Effect.runPromise(hooks.emitDecision('agent.step.before', stepBeforePayload));

// Threshold-triggered LLM compaction
const compressResult = await Effect.runPromise(ctx.compactIfNeeded(state.sessionId, state.projectPath, llm, estimateTokens(messages), config));
if (compressResult.didCompress) {
yield { _tag: 'ReactiveCompact', attempt: 1, released: compressResult.released, promptEstimate: compressResult.promptEstimate };

const rebuilt = Effect.runSync(ctx.build(state.sessionId, state.projectPath));
messages.length = 0;
messages.push(...rebuilt);
state.usage = undefined;
state.promptEstimate = estimateTokens(rebuilt);
}

// Build LLM messages: original messages + step.before transients
const llmMessages = [...messages];

Expand All @@ -209,8 +212,7 @@ export async function* runReActLoop(

const { stream: rawStream, response: respPromise } = llm.completeStream({
messages: llmMessages, system: systemWithCatalog, tools, maxSteps: 1,
signal: opts.abortSignal,
});
}, opts.abortSignal);

for await (const chunk of rawStream) {
if (opts.abortSignal?.aborted) break;
Expand All @@ -221,8 +223,8 @@ export async function* runReActLoop(
if (!llmResult.ok) {
if (llmResult.error.code === 'CONTEXT_OVERFLOW' && attempt < maxOverflowRetries) {
const aggressiveConfig = { ...config, keepRecentTurns: config.reactiveCompactKeepTurns };
const compressResult = await Effect.runPromise(ctx.compress(state.sessionId, state.projectPath, null, aggressiveConfig));
yield { _tag: 'ReactiveCompact', attempt: attempt + 1, released: compressResult.released };
const compressResult = await Effect.runPromise(ctx.compress(state.sessionId, state.projectPath, null, undefined, aggressiveConfig));
yield { _tag: 'ReactiveCompact', attempt: attempt + 1, released: compressResult.released, promptEstimate: compressResult.promptEstimate };
overflow = true;
break;
}
Expand All @@ -242,10 +244,13 @@ export async function* runReActLoop(
}
messages.push(assistantMsg);
yield { _tag: 'Assistant', content: resp.content, toolCalls };
if (resp.usage) {
yield { _tag: 'Usage', prompt: resp.usage.prompt, completion: resp.usage.completion, total: resp.usage.total };
}

if (!toolCalls || toolCalls.length === 0) {
// LLM done — record assistant, then check stop hook
await Effect.runPromise(session.recordAssistant(state, resp.content, toolCalls || [], model));
await Effect.runPromise(session.recordAssistant(state, resp.content, toolCalls || [], model, resp.usage));
const stopDecision: any = await Effect.runPromise(hooks.emitDecision('agent.turn.stop', {
sessionId, content: resp.content, turnId: state.currentTurnId,
}));
Expand Down Expand Up @@ -287,7 +292,7 @@ export async function* runReActLoop(
// Execute tool calls — record assistant, execute batch, record results in one pipeline
const allResults = await Effect.runPromise(
Effect.gen(function* () {
const record = yield* session.recordAssistant(state, resp.content, toolCalls!, model);
const record = yield* session.recordAssistant(state, resp.content, toolCalls!, model, resp.usage);
const results = yield* executor.executeBatch(toolCalls, state.sessionId, {
turnId: state.currentTurnId,
projectPath,
Expand Down Expand Up @@ -338,9 +343,8 @@ export async function* runReActLoop(

if (overflow) continue;

// Turn completed — snapshot and compact
// Turn completed — snapshot
checkpoint.snapshotFinal(projectPath, state.sessionId, state.currentTurnId);
await Effect.runPromise(ctx.appendTurnEnd(state.sessionId, state.projectPath, llm as any));

// Fire-and-forget memory flush
flushSessionToMemory(state.sessionId, llm).catch(e => logger.error('memory flush failed:', e));
Expand Down
6 changes: 6 additions & 0 deletions packages/codingcode/src/client/direct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ export async function* agentEventToStreamChunk(
case 'TodoUpdate':
yield { type: 'todo_update', items: event.items as any };
break;
case 'Usage':
yield { type: 'usage', prompt: event.prompt, completion: event.completion, total: event.total };
break;
case 'ReactiveCompact':
yield { type: 'reactive_compact', released: event.released, promptEstimate: event.promptEstimate };
break;
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/codingcode/src/client/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ export async function createHttpClient(serverUrl: string): Promise<AgentClient>
case 'todo_update':
yield { type: 'todo_update', items: data.items as any };
break;
case 'usage':
yield { type: 'usage', prompt: data.prompt as number, completion: data.completion as number, total: data.total as number };
break;
case 'error':
throw new Error(data.message as string);
case 'done':
Expand Down
6 changes: 6 additions & 0 deletions packages/codingcode/src/client/http/agent-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export function createHttpAgentClient(
case 'todo_update':
yield { type: 'todo_update', items: data.items as any };
break;
case 'usage':
yield { type: 'usage', prompt: data.prompt as number, completion: data.completion as number, total: data.total as number };
break;
case 'reactive_compact':
yield { type: 'reactive_compact', released: data.released as number, promptEstimate: data.promptEstimate as number };
break;
case 'error':
throw new Error(data.message as string);
case 'done':
Expand Down
4 changes: 3 additions & 1 deletion packages/codingcode/src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export type StreamChunk =
| { type: 'tool_denied'; id: string; name: string; reason: string }
| { type: 'error'; message: string }
| { type: 'done' }
| { type: 'todo_update'; items: ReadonlyArray<{ step: string; status: string }> };
| { type: 'todo_update'; items: ReadonlyArray<{ step: string; status: string }> }
| { type: 'usage'; prompt: number; completion: number; total: number }
| { type: 'reactive_compact'; released: number; promptEstimate: number };

export interface AgentClient {
sendMessage(input: string, cwd?: string): AsyncGenerator<StreamChunk>;
Expand Down
Loading