From f6da5ded83bcf62abea44715bb6bdde30710a573 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Tue, 24 Feb 2026 13:00:54 +0100 Subject: [PATCH] refactor: move dynamic editor context from system prompt to user message Split editor context into static and dynamic parts: - Static (system prompt, cached): date, timezone, shell These rarely change and benefit from prompt caching. - Dynamic (user message, per-turn): active file, visible files, open tabs These change frequently as the user switches files/tabs. Injected as a synthetic block on each user message so the model always has fresh context. This matches the pattern used by the VS Code extension, where getEnvironmentDetails() is called before every agent turn. --- .../opencode/src/kilocode/editor-context.ts | 42 ++++++++++++------- packages/opencode/src/session/prompt.ts | 15 +++++++ packages/opencode/src/session/system.ts | 4 +- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/kilocode/editor-context.ts b/packages/opencode/src/kilocode/editor-context.ts index 06085e9301f..f5da9bfa33d 100644 --- a/packages/opencode/src/kilocode/editor-context.ts +++ b/packages/opencode/src/kilocode/editor-context.ts @@ -6,36 +6,50 @@ export interface EditorContext { timezone?: string } -function formatDate(timezone?: string): string[] { +/** + * Build static lines from editor context. + * These rarely change during a session and belong in the system prompt + * so they benefit from prompt caching. + */ +export function staticEnvLines(ctx?: EditorContext): string[] { const now = new Date() const lines = [` Today's date: ${now.toDateString()}`] - if (timezone) { + if (ctx?.timezone) { const offset = -now.getTimezoneOffset() const sign = offset >= 0 ? "+" : "-" const hours = Math.floor(Math.abs(offset) / 60) const mins = Math.abs(offset) % 60 - lines.push(` User timezone: ${timezone}, UTC${sign}${hours}:${mins.toString().padStart(2, "0")}`) + lines.push(` User timezone: ${ctx.timezone}, UTC${sign}${hours}:${mins.toString().padStart(2, "0")}`) + } + if (ctx?.shell) { + lines.push(` Default shell: ${ctx.shell}`) } return lines } /** - * Build additional lines from VS Code editor context. - * Returns an array of pre-formatted ` key: value` strings. + * Build a per-message block from editor context. + * These change frequently (user switches files/tabs) and belong in the + * user message so the model always has fresh context. + * Returns undefined when there is nothing dynamic to report. */ -export function editorContextEnvLines(ctx?: EditorContext): string[] { - const lines = formatDate(ctx?.timezone) - if (ctx?.shell) { - lines.push(` Default shell: ${ctx.shell}`) - } +export function environmentDetails(ctx?: EditorContext): string | undefined { + const lines: string[] = [] if (ctx?.activeFile) { - lines.push(` Active file: ${ctx.activeFile}`) + lines.push(`Active file: ${ctx.activeFile}`) } if (ctx?.visibleFiles?.length) { - lines.push(` Visible files: ${ctx.visibleFiles.join(", ")}`) + lines.push(`Visible files:`) + for (const f of ctx.visibleFiles) { + lines.push(` ${f}`) + } } if (ctx?.openTabs?.length) { - lines.push(` Open tabs: ${ctx.openTabs.join(", ")}`) + lines.push(`Open tabs:`) + for (const f of ctx.openTabs) { + lines.push(` ${f}`) + } } - return lines + if (lines.length === 0) return undefined + return ["", ...lines, ""].join("\n") } diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 6779ecc6e12..f90e55ac918 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -47,6 +47,7 @@ import { iife } from "@/util/iife" import { Shell } from "@/shell/shell" import { Truncate } from "@/tool/truncation" import { PlanFollowup } from "@/kilocode/plan-followup" // kilocode_change +import { environmentDetails } from "@/kilocode/editor-context" // kilocode_change // @ts-ignore globalThis.AI_SDK_LOG_WARNINGS = false @@ -1355,6 +1356,20 @@ export namespace SessionPrompt { }), ).then((x) => x.flat().map(assign)) + // kilocode_change start — inject dynamic editor context into user message + const envBlock = environmentDetails(input.editorContext) + if (envBlock) { + parts.push({ + id: Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "text", + text: envBlock, + synthetic: true, + }) + } + // kilocode_change end + await Plugin.trigger( "chat.message", { diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index 4c793c014b8..348fd881062 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -13,7 +13,7 @@ import type { Provider } from "@/provider/provider" // kilocode_change start import SOUL from "../kilocode/soul.txt" -import { editorContextEnvLines, type EditorContext } from "../kilocode/editor-context" +import { staticEnvLines, type EditorContext } from "../kilocode/editor-context" // kilocode_change end export namespace SystemPrompt { @@ -67,7 +67,7 @@ export namespace SystemPrompt { ` Working directory: ${Instance.directory}`, ` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`, ` Platform: ${process.platform}`, - ...editorContextEnvLines(editorContext), // kilocode_change + ...staticEnvLines(editorContext), // kilocode_change ``, ``, ` ${