diff --git a/packages/opencode/cache.ts b/packages/opencode/cache.ts new file mode 100644 index 0000000..0aacf05 --- /dev/null +++ b/packages/opencode/cache.ts @@ -0,0 +1,65 @@ +import { + getEnabledToolNames, + loadKompassConfig, + mergeWithDefaults, + resolveAgents, + resolveCommands, + type MergedKompassConfig, + type ResolvedAgentDefinition, + type ResolvedCommandDefinition, +} from "../core/index.ts"; +import { + getConfiguredOpenCodeToolName, + prefixKompassToolReferences, +} from "./tool-names.ts"; + +const mergedConfigCache = new Map>(); +const configuredToolNamesCache = new Map>>(); +const resolvedAgentsCache = new Map>>(); +const resolvedCommandsCache = new Map>>(); + +function readThroughCache(cache: Map>, key: string, load: () => Promise): Promise { + const cached = cache.get(key); + if (cached) return cached; + + const pending = load().catch((error) => { + cache.delete(key); + throw error; + }); + cache.set(key, pending); + return pending; +} + +export function loadMergedKompassConfig(projectRoot: string): Promise { + return readThroughCache(mergedConfigCache, projectRoot, async () => { + const userConfig = await loadKompassConfig(projectRoot); + return mergeWithDefaults(userConfig); + }); +} + +export function loadConfiguredToolNames(projectRoot: string): Promise> { + return readThroughCache(configuredToolNamesCache, projectRoot, async () => { + const config = await loadMergedKompassConfig(projectRoot); + return Object.fromEntries( + getEnabledToolNames(config.tools).map((toolName) => [ + toolName, + getConfiguredOpenCodeToolName(toolName, config.tools[toolName].name), + ]), + ); + }); +} + +export async function rewriteKompassToolReferences(projectRoot: string, input: string): Promise { + const configuredToolNames = await loadConfiguredToolNames(projectRoot); + return prefixKompassToolReferences(input, configuredToolNames); +} + +export function loadResolvedAgents(projectRoot: string): Promise> { + return readThroughCache(resolvedAgentsCache, projectRoot, () => resolveAgents(projectRoot)); +} + +export function loadResolvedCommands(projectRoot: string): Promise> { + const ciKey = process.env.CI ? "ci" : "non-ci"; + const cacheKey = `${projectRoot}:${ciKey}`; + return readThroughCache(resolvedCommandsCache, cacheKey, () => resolveCommands(projectRoot)); +} diff --git a/packages/opencode/config.ts b/packages/opencode/config.ts index 36746ab..9c58f7d 100644 --- a/packages/opencode/config.ts +++ b/packages/opencode/config.ts @@ -1,17 +1,7 @@ import type { Config } from "@opencode-ai/plugin"; import type { AgentConfig } from "@opencode-ai/sdk"; -import { - getEnabledToolNames, - loadKompassConfig, - mergeWithDefaults, - resolveAgents, - resolveCommands, -} from "../core/index.ts"; -import { - getConfiguredOpenCodeToolName, - prefixKompassToolReferences, -} from "./tool-names.ts"; +import { loadResolvedAgents, loadResolvedCommands, rewriteKompassToolReferences } from "./cache.ts"; import type { PluginLogger } from "./logging.ts"; type ApplyConfigOptions = { @@ -23,16 +13,7 @@ export async function applyAgentsConfig( projectRoot: string, options?: ApplyConfigOptions, ) { - const userConfig = await loadKompassConfig(projectRoot); - const config = mergeWithDefaults(userConfig); - const agents = await resolveAgents(projectRoot); - const configuredToolNames = Object.fromEntries( - getEnabledToolNames(config.tools).map((toolName) => [ - toolName, - getConfiguredOpenCodeToolName(toolName, config.tools[toolName].name), - ]), - ); - const rewriteToolNames = (input: string) => prefixKompassToolReferences(input, configuredToolNames); + const agents = await loadResolvedAgents(projectRoot); cfg.agent ??= {}; @@ -40,7 +21,7 @@ export async function applyAgentsConfig( const agentConfig: AgentConfig = { description: definition.description, permission: definition.permission, - ...(definition.prompt ? { prompt: rewriteToolNames(definition.prompt) } : {}), + ...(definition.prompt ? { prompt: await rewriteKompassToolReferences(projectRoot, definition.prompt) } : {}), ...(definition.mode ? { mode: definition.mode } : {}), }; cfg.agent[name] = agentConfig; @@ -58,16 +39,7 @@ export async function applyCommandsConfig( projectRoot: string, options?: ApplyConfigOptions, ) { - const userConfig = await loadKompassConfig(projectRoot); - const config = mergeWithDefaults(userConfig); - const commands = await resolveCommands(projectRoot); - const configuredToolNames = Object.fromEntries( - getEnabledToolNames(config.tools).map((toolName) => [ - toolName, - getConfiguredOpenCodeToolName(toolName, config.tools[toolName].name), - ]), - ); - const rewriteToolNames = (input: string) => prefixKompassToolReferences(input, configuredToolNames); + const commands = await loadResolvedCommands(projectRoot); cfg.command ??= {}; @@ -76,7 +48,7 @@ export async function applyCommandsConfig( description: definition.description, agent: definition.agent, subtask: definition.subtask, - template: rewriteToolNames(definition.template), + template: await rewriteKompassToolReferences(projectRoot, definition.template), }; options?.logger?.info("Loaded Kompass command", { diff --git a/packages/opencode/index.ts b/packages/opencode/index.ts index fce9084..87adc02 100644 --- a/packages/opencode/index.ts +++ b/packages/opencode/index.ts @@ -9,11 +9,10 @@ import { createTicketLoadTool, createTicketSyncTool, getEnabledToolNames, - loadKompassConfig, - mergeWithDefaults, type MergedKompassConfig, type Shell, } from "../core/index.ts"; +import { loadConfiguredToolNames, loadMergedKompassConfig } from "./cache.ts"; import { applyAgentsConfig, applyCommandsConfig } from "./config.ts"; import { createPluginLogger, getErrorDetails, type PluginLogger } from "./logging.ts"; import { @@ -144,16 +143,6 @@ const opencodeToolCreators: Record = { }); }, command_expansion(_: PluginInput["$"], _client: PluginInput["client"], config: MergedKompassConfig, projectRoot: string) { - const configuredToolNames = Object.fromEntries( - getEnabledToolNames(config.tools).map((toolName) => [ - toolName, - getConfiguredOpenCodeToolName(toolName, config.tools[toolName].name), - ]), - ); - const definition = createCommandExpansionTool(projectRoot, { - rewriteBody: (body) => prefixKompassToolReferences(body, configuredToolNames), - }); - return tool({ description: "Expand a delegated command body into a runnable prompt for immediate task execution.", args: { @@ -161,6 +150,11 @@ const opencodeToolCreators: Record = { body: tool.schema.string().describe("Literal body content from the delegate block").optional(), }, execute: async (args, context) => { + const configuredToolNames = await loadConfiguredToolNames(projectRoot); + const definition = createCommandExpansionTool(projectRoot, { + rewriteBody: (body) => prefixKompassToolReferences(body, configuredToolNames), + }); + context.metadata({ title: `Command /${args.command.trim()}`, metadata: { @@ -269,8 +263,7 @@ export async function createOpenCodeTools( client: PluginInput["client"], projectRoot: string, ): Promise> { - const userConfig = await loadKompassConfig(projectRoot); - const config = mergeWithDefaults(userConfig); + const config = await loadMergedKompassConfig(projectRoot); const tools: Record = {}; const logger = createPluginLogger(client, projectRoot); diff --git a/packages/opencode/package.json b/packages/opencode/package.json index a8890a8..bb7f31f 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "name": "@kompassdev/opencode", - "version": "0.12.1", + "version": "0.13.0", "description": "OpenCode plugin for navigating repos with fewer wrong turns", "type": "module", "main": "./dist/plugin.js",