Madinah |

kode-cli 上下文管理

Date: Oct 20, 2024 Sun
|
Estimated Reading Time: 1 min
|
Author: Madinah

Kode 完整上下文管理流程

架构概览

 用户输入 → REPL → query() → formatSystemPromptWithContext() → LLM
                ↓                        ↓
            getContext()          generateSystemReminders()
                ↓                        ↓
           [静态上下文]              [动态 Reminders] 

完整数据流程

1. 用户启动会话

 // src/screens/REPL.tsx - 初始化阶段

// 用户启动 Kode CLI
$ kode

// REPL 组件初始化
const [messages, setMessages] = useState<MessageType[]>([])
const [context, setContext] = useState<{[k: string]: string}>({})

// 异步加载静态上下文(会话开始时一次性加载)
useEffect(() => {
  async function loadContext() {
    const ctx = await getContext()
    setContext(ctx)
  }
  loadContext()
}, [])
 

2. 静态上下文收集 (src/context.ts)

 // getContext() 返回的数据结构
const staticContext = {
  // 1. 目录结构(memoized,会话期间不变)
  directoryStructure: `
Below is a snapshot of this project's file structure:
.
├── src/
│   ├── screens/
│   │   └── REPL.tsx
│   ├── services/
│   │   ├── claude.ts
│   │   └── systemReminder.ts
│   ├── tools/
│   │   ├── TodoWriteTool/
│   │   └── FileReadTool/
│   └── utils/
│       ├── context.ts
│       └── todoStorage.ts
├── package.json
└── README.md
`,

  // 2. Git 状态(memoized)
  gitStatus: `
Current branch: feature/context-management
Main branch: main

Status:
M  src/services/claude.ts
M  src/query.ts
?? docs/context-flow.md

Recent commits:
abc1234 Add context management
def5678 Implement reminders
ghi9012 Fix todo storage

Your recent commits:
abc1234 Add context management
`,

  // 3. 代码风格(memoized)
  codeStyle: `
Project uses:
- TypeScript with strict mode
- React with Ink for CLI UI
- Zod for schema validation
- Prettier for formatting
`,

  // 4. README 内容
  readme: `
# Kode CLI

AI-powered coding assistant...
`,

  // 5. 项目文档(AGENTS.md + CLAUDE.md)
  projectDocs: `
# AGENTS.md

This file provides guidance to Kode automation agents...

---

# CLAUDE.md

Additional project-specific instructions...
`,

  // 6. 用户自定义上下文(从配置文件)
  customContext: `
Team conventions:
- Use functional components
- Prefer async/await over promises
`,
}; 

3. 用户发送消息

 // 用户输入
const userInput = '帮我实现一个新的 tool';

// PromptInput 组件处理
const handleSubmit = async (input: string) => {
  setIsLoading(true);

  // 创建用户消息
  const userMessage: UserMessage = {
    type: 'user',
    uuid: crypto.randomUUID(),
    message: {
      role: 'user',
      content: input,
    },
  };

  // 添加到消息历史
  setMessages((prev) => [...prev, userMessage]);

  // 开始查询
  const abortController = new AbortController();
  setAbortController(abortController);

  // 调用 query 生成器
  for await (const message of query(
    [...messages, userMessage], // 消息历史
    getSystemPrompt(), // 系统提示
    context, // 静态上下文
    canUseTool, // 权限检查函数
    {
      abortController,
      options: {
        commands,
        forkNumber,
        messageLogName,
        tools,
        verbose,
        safeMode,
        maxThinkingTokens: getMaxThinkingTokens(),
        model: 'main',
      },
      readFileTimestamps: {},
      setToolJSX,
      agentId: 'default',
    },
    getBinaryFeedbackResponse,
  )) {
    setMessages((prev) => [...prev, message]);
  }

  setIsLoading(false);
}; 

4. query() 函数处理 (src/query.ts)

 export async function* query(
  messages: Message[],
  systemPrompt: string[],
  context: { [k: string]: string },
  canUseTool: CanUseToolFn,
  toolUseContext: ExtendedToolUseContext,
  getBinaryFeedbackResponse?: (m1, m2) => Promise<BinaryFeedbackResult>,
): AsyncGenerator<Message, void> {
  // 📊 当前状态
  console.log('=== Query Start ===');
  console.log('Messages count:', messages.length);
  console.log('Context keys:', Object.keys(context));
  console.log('Agent ID:', toolUseContext.agentId);

  // 🔄 自动压缩检查
  const { messages: processedMessages, wasCompacted } = await checkAutoCompact(
    messages,
    toolUseContext,
  );

  if (wasCompacted) {
    console.log(
      '✅ Messages compacted:',
      messages.length,
      '→',
      processedMessages.length,
    );
    messages = processedMessages;
  }

  // 🎯 格式化系统提示 + 生成动态 reminders
  const { systemPrompt: fullSystemPrompt, reminders } =
    formatSystemPromptWithContext(
      systemPrompt,
      context,
      toolUseContext.agentId,
    );

  console.log('System prompt blocks:', fullSystemPrompt.length);
  console.log('Reminders generated:', reminders ? 'Yes' : 'No');

  // 📢 触发会话启动事件
  emitReminderEvent('session:startup', {
    agentId: toolUseContext.agentId,
    messages: messages.length,
    timestamp: Date.now(),
  });

  // 💉 注入 reminders 到最后一条用户消息
  if (reminders && messages.length > 0) {
    for (let i = messages.length - 1; i >= 0; i--) {
      if (messages[i]?.type === 'user') {
        const lastUserMessage = messages[i] as UserMessage;

        // 前置注入 reminders
        messages[i] = {
          ...lastUserMessage,
          message: {
            ...lastUserMessage.message,
            content: reminders + lastUserMessage.message.content,
          },
        };

        console.log('✅ Reminders injected to message', i);
        break;
      }
    }
  }

  // 🤖 调用 LLM
  const result = await queryWithBinaryFeedback(
    toolUseContext,
    () =>
      queryLLM(
        normalizeMessagesForAPI(messages),
        fullSystemPrompt,
        toolUseContext.options.maxThinkingTokens,
        toolUseContext.options.tools,
        toolUseContext.abortController.signal,
        {
          safeMode: toolUseContext.options.safeMode,
          model: toolUseContext.options.model || 'main',
          prependCLISysprompt: true,
          toolUseContext,
        },
      ),
    getBinaryFeedbackResponse,
  );

  // 返回 AI 响应
  if (result.message) {
    yield result.message;
  }

  // 🔧 处理工具调用...
  // (省略工具执行逻辑)
} 

5. formatSystemPromptWithContext() (src/services/claude.ts)

 export function formatSystemPromptWithContext(
  systemPrompt: string[],
  context: { [k: string]: string },
  agentId?: string,
  skipContextReminders = false,
): { systemPrompt: string[]; reminders: string } {
  const enhancedPrompt = [...systemPrompt];
  let reminders = '';

  // 📊 输入数据示例
  console.log('=== formatSystemPromptWithContext ===');
  console.log('Input systemPrompt:', systemPrompt.slice(0, 2));
  console.log('Input context keys:', Object.keys(context));
  console.log('Agent ID:', agentId);

  // 🎯 步骤 0: GPT-5 特殊处理
  const modelManager = getModelManager();
  const modelProfile = modelManager.getModel('main');

  if (modelProfile && isGPT5Model(modelProfile.modelName)) {
    enhancedPrompt.push(
      '\n# Agent Persistence for Long-Running Coding Tasks',
      'You are working on a coding project...',
      // ... 更多持久化指令
    );
    console.log('✅ Added GPT-5 persistence prompts');
  }

  // 🔍 检查是否有上下文
  const hasContext = Object.entries(context).length > 0;
  console.log('Has context:', hasContext);

  if (hasContext) {
    // 📄 步骤 1: 注入 Kode 项目文档到系统提示
    if (!skipContextReminders) {
      const kodeContext = generateKodeContext();

      if (kodeContext) {
        enhancedPrompt.push('\n---\n# 项目上下文\n');
        enhancedPrompt.push(kodeContext);
        enhancedPrompt.push('\n---\n');

        console.log('✅ Kode context injected:', kodeContext.length, 'chars');
      }
    }

    // 🔔 步骤 2: 生成动态 reminders
    const reminderMessages = generateSystemReminders(hasContext, agentId);

    if (reminderMessages.length > 0) {
      reminders = reminderMessages.map((r) => r.content).join('\n') + '\n';

      console.log('✅ Generated reminders:', reminderMessages.length);
      console.log(
        'Reminder types:',
        reminderMessages.map((r) => r.type).join(', '),
      );
    }

    // 📦 步骤 3: 添加其他静态上下文
    enhancedPrompt.push(
      `\nAs you answer the user's questions, you can use the following context:\n`,
    );

    // 过滤掉已处理的项目文档
    const filteredContext = Object.fromEntries(
      Object.entries(context).filter(
        ([key]) => key !== 'projectDocs' && key !== 'userDocs',
      ),
    );

    enhancedPrompt.push(
      ...Object.entries(filteredContext).map(
        ([key, value]) => `<context name="${key}">${value}</context>`,
      ),
    );

    console.log(
      '✅ Added context blocks:',
      Object.keys(filteredContext).join(', '),
    );
  }

  // 📤 输出数据示例
  console.log('=== Output ===');
  console.log('Enhanced prompt blocks:', enhancedPrompt.length);
  console.log('Reminders length:', reminders.length);

  return { systemPrompt: enhancedPrompt, reminders };
} 

6. generateSystemReminders() (src/services/systemReminder.ts)

 public generateReminders(
  hasContext: boolean = false,
  agentId?: string
): ReminderMessage[] {

  console.log('=== generateSystemReminders ===')
  console.log('Has context:', hasContext)
  console.log('Agent ID:', agentId)

  // 🚫 无上下文时不生成
  if (!hasContext) {
    console.log('❌ No context, skipping reminders')
    return []
  }

  // 🚫 达到会话限制
  if (this.sessionState.reminderCount >=
      this.sessionState.config.maxRemindersPerSession) {
    console.log('❌ Reminder limit reached:',
      this.sessionState.reminderCount)
    return []
  }

  const reminders: ReminderMessage[] = []

  // 🔄 懒加载生成器
  const reminderGenerators = [
    () => this.dispatchTodoEvent(agentId),
    () => this.dispatchSecurityEvent(),
    () => this.dispatchPerformanceEvent(),
    () => this.getMentionReminders()
  ]

  for (const generator of reminderGenerators) {
    if (reminders.length >= 5) break

    const result = generator()
    if (result) {
      const remindersToAdd = Array.isArray(result) ? result : [result]
      reminders.push(...remindersToAdd)
      this.sessionState.reminderCount += remindersToAdd.length

      console.log('✅ Added reminder:',
        remindersToAdd.map(r => r.type).join(', '))
    }
  }

  console.log('=== Total reminders ===', reminders.length)

  return reminders
}

// Todo Reminder 示例
private dispatchTodoEvent(agentId?: string): ReminderMessage | null {
  const todos = getTodos(agentId)
  const agentKey = agentId || 'default'

  console.log('📋 Checking todos for agent:', agentKey)
  console.log('Todo count:', todos.length)

  // 场景 1: 空列表提醒
  if (todos.length === 0 &&
      !this.sessionState.remindersSent.has(`todo_empty_${agentKey}`)) {

    this.sessionState.remindersSent.add(`todo_empty_${agentKey}`)

    console.log('✅ Generated empty todo reminder')

    return this.createReminderMessage(
      'todo',
      'task',
      'medium',
      'Your todo list is currently empty. Use TodoWrite if needed.',
      Date.now()
    )
  }

  // 场景 2: Todo 更新提醒
  if (todos.length > 0) {
    const stateHash = this.getTodoStateHash(todos)
    const reminderKey = `todo_updated_${agentKey}_${todos.length}_${stateHash}`

    if (!this.sessionState.remindersSent.has(reminderKey)) {
      this.sessionState.remindersSent.add(reminderKey)
      this.clearTodoReminders(agentKey)

      const todoContent = JSON.stringify(
        todos.map(todo => ({
          content: todo.content.substring(0, 100),
          status: todo.status,
          priority: todo.priority,
          id: todo.id
        }))
      )

      console.log('✅ Generated todo update reminder')
      console.log('Todo content preview:', todoContent.substring(0, 100))

      return this.createReminderMessage(
        'todo',
        'task',
        'medium',
        `Your todo list has changed:\n${todoContent}`,
        Date.now()
      )
    }
  }

  console.log('❌ No todo reminder needed')
  return null
}

 

7. 最终发送给 LLM 的数据结构

 // queryLLM() 接收的参数
const llmRequest = {
  // 消息历史(已注入 reminders)
  messages: [
    {
      role: 'user',
      content: `<system-reminder>
Your todo list has changed:
[{"content":"实现新 tool","status":"in_progress","priority":"high","id":"task-1"}]
</system-reminder>

帮我实现一个新的 tool`
    },
    {
      role: 'assistant',
      content: [
        { type: 'text', text: '好的,我来帮你...' },
        { type: 'tool_use', name: 'FileRead', input: {...} }
      ]
    },
    {
      role: 'user',
      content: [
        {
          type: 'tool_result',
          tool_use_id: 'toolu_123',
          content: '文件内容...'
        }
      ]
    }
  ],

  // 系统提示(已增强)
  systemPrompt: [
    // 基础系统提示
    "You are Kode, an AI coding assistant...",

    // GPT-5 持久化指令(如果适用)
    "\n# Agent Persistence for Long-Running Coding Tasks",
    "You are working on a coding project...",

    // Kode 项目文档
    "\n---\n# 项目上下文\n",
    "# AGENTS.md\n\nThis file provides guidance...",
    "\n---\n",

    // 静态上下文
    "\nAs you answer the user's questions, you can use the following context:\n",
    '<context name="directoryStructure">...</context>',
    '<context name="gitStatus">...</context>',
    '<context name="codeStyle">...</context>',
    '<context name="readme">...</context>'
  ],

  // 其他参数
  maxThinkingTokens: 10000,
  tools: [...],  // 工具定义
  temperature: 1.0,
  model: 'claude-sonnet-4-20250514'
}

 

完整示例:从用户输入到 LLM 响应

 // ============================================
// 场景:用户要求实现新功能
// ============================================

// 1️⃣ 用户输入
用户: "帮我实现一个文件搜索工具"

// 2️⃣ REPL 处理
const userMessage = {
  type: 'user',
  uuid: 'msg-001',
  message: {
    role: 'user',
    content: '帮我实现一个文件搜索工具'
  }
}

// 3️⃣ 静态上下文(已缓存)
const context = {
  directoryStructure: "src/\n  tools/\n    FileReadTool/\n    ...",
  gitStatus: "Current branch: main\nStatus: clean",
  codeStyle: "TypeScript + React + Zod",
  projectDocs: "# AGENTS.md\n\n工具开发指南..."
}

// 4️⃣ 调用 query()
for await (const message of query(
  [userMessage],
  ["You are Kode..."],
  context,
  canUseTool,
  toolUseContext
)) {
  // 处理响应
}

// 5️⃣ formatSystemPromptWithContext()
// 输入:
{
  systemPrompt: ["You are Kode..."],
  context: { directoryStructure: "...", gitStatus: "...", ... },
  agentId: "default"
}

// 输出:
{
  systemPrompt: [
    "You are Kode...",
    "\n---\n# 项目上下文\n",
    "# AGENTS.md\n\n工具开发指南...",
    "\n---\n",
    "\nAs you answer...\n",
    '<context name="directoryStructure">...</context>',
    '<context name="gitStatus">...</context>'
  ],
  reminders: "<system-reminder>\nYour todo list is empty...\n</system-reminder>\n"
}

// 6️⃣ 注入 reminders
messages[0].message.content =
  "<system-reminder>\nYour todo list is empty...\n</system-reminder>\n" +
  "帮我实现一个文件搜索工具"

// 7️⃣ 发送给 LLM
API Request: {
  model: "claude-sonnet-4-20250514",
  messages: [
    {
      role: "user",
      content: "<system-reminder>...</system-reminder>\n帮我实现一个文件搜索工具"
    }
  ],
  system: [
    { type: "text", text: "You are Kode..." },
    { type: "text", text: "\n---\n# 项目上下文\n" },
    { type: "text", text: "# AGENTS.md\n\n..." },
    { type: "text", text: '<context name="directoryStructure">...</context>' },
    { type: "text", text: '<context name="gitStatus">...</context>',
      cache_control: { type: "ephemeral" } }  // 缓存控制
  ],
  tools: [...],
  max_tokens: 8192,
  temperature: 1.0
}

// 8️⃣ LLM 响应
{
  role: "assistant",
  content: [
    {
      type: "text",
      text: "我来帮你实现一个文件搜索工具。根据项目结构,我会创建..."
    },
    {
      type: "tool_use",
      name: "FileWrite",
      input: {
        path: "src/tools/FileSearchTool/FileSearchTool.tsx",
        content: "import { Tool } from '@tool'..."
      }
    }
  ]
}

// 9️⃣ 工具执行
// FileWrite 工具被调用,创建新文件

// 🔟 返回给用户
文件已创建: src/tools/FileSearchTool/FileSearchTool.tsx
 

关键设计要点

  • 静态上下文缓存:getContext() 使用 memoize,会话期间只加载一次
  • 动态 Reminders:每次查询时实时生成,基于当前状态
  • 分离注入:项目文档注入系统提示,reminders 注入用户消息
  • 去重机制:使用 remindersSent Set 避免重复提醒
  • 优先级管理:最多 5 个 reminders,按优先级选择
  • 缓存控制:长文本使用 prompt caching 减少成本
  • 事件驱动:通过事件系统解耦各模块

This work is licensed under CC BY-NC-SA 4.0