diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 09e98a3cda2..52bac9509fb 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -2334,15 +2334,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper // Shell const shell = vscode.env.shell || undefined - // Timezone - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || undefined - return { ...(visibleFiles.length > 0 ? { visibleFiles } : {}), ...(openTabs.length > 0 ? { openTabs } : {}), ...(activeFile ? { activeFile } : {}), ...(shell ? { shell } : {}), - ...(timezone ? { timezone } : {}), } } diff --git a/packages/kilo-vscode/src/services/cli-backend/types.ts b/packages/kilo-vscode/src/services/cli-backend/types.ts index 81645299da9..6e8aedb0e49 100644 --- a/packages/kilo-vscode/src/services/cli-backend/types.ts +++ b/packages/kilo-vscode/src/services/cli-backend/types.ts @@ -113,6 +113,4 @@ export interface EditorContext { activeFile?: string /** User's default shell (from vscode.env.shell) */ shell?: string - /** User's timezone (e.g. "Europe/Amsterdam") */ - timezone?: string } diff --git a/packages/opencode/src/kilocode/editor-context.ts b/packages/opencode/src/kilocode/editor-context.ts index 06085e9301f..3a087bc82af 100644 --- a/packages/opencode/src/kilocode/editor-context.ts +++ b/packages/opencode/src/kilocode/editor-context.ts @@ -3,39 +3,55 @@ export interface EditorContext { openTabs?: string[] activeFile?: string shell?: string - timezone?: string } -function formatDate(timezone?: string): string[] { - const now = new Date() - const lines = [` Today's date: ${now.toDateString()}`] - if (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")}`) +/** + * 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 lines: string[] = [] + 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. + * Always includes at least the current timestamp. */ -export function editorContextEnvLines(ctx?: EditorContext): string[] { - const lines = formatDate(ctx?.timezone) - if (ctx?.shell) { - lines.push(` Default shell: ${ctx.shell}`) - } +function timestamp(): string { + const now = new Date() + const offset = -now.getTimezoneOffset() + const sign = offset >= 0 ? "+" : "-" + const h = Math.floor(Math.abs(offset) / 60) + .toString() + .padStart(2, "0") + const m = (Math.abs(offset) % 60).toString().padStart(2, "0") + const pad = (n: number) => n.toString().padStart(2, "0") + return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${sign}${h}:${m}` +} + +export function environmentDetails(ctx?: EditorContext): string { + const lines: string[] = [`Current time: ${timestamp()}`] 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 + return ["", ...lines, ""].join("\n") } diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index f34765451b8..e468d1f2604 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -375,7 +375,6 @@ export namespace MessageV2 { openTabs: z.array(z.string()).optional(), activeFile: z.string().optional(), shell: z.string().optional(), - timezone: z.string().optional(), }) .optional(), // kilocode_change end diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 6779ecc6e12..3345aca761e 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 @@ -130,7 +131,6 @@ export namespace SessionPrompt { openTabs: z.array(z.string()).optional(), activeFile: z.string().optional(), shell: z.string().optional(), - timezone: z.string().optional(), }) .optional(), // kilocode_change end @@ -324,6 +324,12 @@ export namespace SessionPrompt { // on the user message and will be retrieved from lastUser below let structuredOutput: unknown | undefined + // kilocode_change — cache environment details per turn so the last user + // message stays byte-identical across tool-loop steps (prompt caching). + // Keyed by user message ID so it recomputes when a new user message arrives. + let envBlock: string | undefined + let envUser: string | undefined + let step = 0 const session = await Session.get(sessionID) while (true) { @@ -556,6 +562,7 @@ export namespace SessionPrompt { }, agent: lastUser.agent, model: lastUser.model, + editorContext: lastUser.editorContext, // kilocode_change — preserve editor context } await Session.updateMessage(summaryUserMsg) await Session.updatePart({ @@ -694,6 +701,30 @@ export namespace SessionPrompt { await Plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) + // kilocode_change start — ephemerally inject dynamic editor context into last user message + if (envUser !== lastUser.id) { + envBlock = environmentDetails(lastUser.editorContext) + envUser = lastUser.id + } + if (envBlock) { + const idx = msgs.findLastIndex((m) => m.info.role === "user") + if (idx !== -1) + msgs[idx] = { + ...msgs[idx], + parts: [ + ...msgs[idx].parts, + { + id: Identifier.ascending("part"), + sessionID, + messageID: msgs[idx].info.id, + type: "text", + text: envBlock, + } satisfies MessageV2.TextPart, + ], + } + } + // kilocode_change end + // Build system prompt, adding structured output instruction if needed const system = [ ...(await SystemPrompt.environment(model, lastUser.editorContext)), 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 ``, ``, ` ${ diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 82ef771ef72..a1c830a2ca4 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -1943,7 +1943,6 @@ export class Session2 extends HeyApiClient { openTabs?: Array activeFile?: string shell?: string - timezone?: string } parts?: Array }, @@ -2083,7 +2082,6 @@ export class Session2 extends HeyApiClient { openTabs?: Array activeFile?: string shell?: string - timezone?: string } parts?: Array }, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 8c6a10a5427..775b80251ad 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -143,7 +143,6 @@ export type UserMessage = { openTabs?: Array activeFile?: string shell?: string - timezone?: string } } @@ -3428,7 +3427,6 @@ export type SessionPromptData = { openTabs?: Array activeFile?: string shell?: string - timezone?: string } parts: Array } @@ -3668,7 +3666,6 @@ export type SessionPromptAsyncData = { openTabs?: Array activeFile?: string shell?: string - timezone?: string } parts: Array } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index c65a4ad275e..a83a8e21ef3 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -3186,9 +3186,6 @@ }, "shell": { "type": "string" - }, - "timezone": { - "type": "string" } } }, @@ -3696,9 +3693,6 @@ }, "shell": { "type": "string" - }, - "timezone": { - "type": "string" } } }, @@ -8581,9 +8575,6 @@ }, "shell": { "type": "string" - }, - "timezone": { - "type": "string" } } } @@ -12027,6 +12018,10 @@ "description": "Enable the batch tool", "type": "boolean" }, + "codebase_search": { + "description": "Enable AI-powered codebase search", + "type": "boolean" + }, "openTelemetry": { "description": "Enable telemetry. Set to false to opt-out.", "default": true,