Skip to content
Merged
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
65 changes: 65 additions & 0 deletions packages/opencode/cache.ts
Original file line number Diff line number Diff line change
@@ -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<string, Promise<MergedKompassConfig>>();
const configuredToolNamesCache = new Map<string, Promise<Record<string, string>>>();
const resolvedAgentsCache = new Map<string, Promise<Record<string, ResolvedAgentDefinition>>>();
const resolvedCommandsCache = new Map<string, Promise<Record<string, ResolvedCommandDefinition>>>();

function readThroughCache<T>(cache: Map<string, Promise<T>>, key: string, load: () => Promise<T>): Promise<T> {
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<MergedKompassConfig> {
return readThroughCache(mergedConfigCache, projectRoot, async () => {
const userConfig = await loadKompassConfig(projectRoot);
return mergeWithDefaults(userConfig);
});
}

export function loadConfiguredToolNames(projectRoot: string): Promise<Record<string, string>> {
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<string> {
const configuredToolNames = await loadConfiguredToolNames(projectRoot);
return prefixKompassToolReferences(input, configuredToolNames);
}

export function loadResolvedAgents(projectRoot: string): Promise<Record<string, ResolvedAgentDefinition>> {
return readThroughCache(resolvedAgentsCache, projectRoot, () => resolveAgents(projectRoot));
}

export function loadResolvedCommands(projectRoot: string): Promise<Record<string, ResolvedCommandDefinition>> {
const ciKey = process.env.CI ? "ci" : "non-ci";
const cacheKey = `${projectRoot}:${ciKey}`;
return readThroughCache(resolvedCommandsCache, cacheKey, () => resolveCommands(projectRoot));
}
38 changes: 5 additions & 33 deletions packages/opencode/config.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -23,24 +13,15 @@ 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 ??= {};

for (const [name, definition] of Object.entries(agents)) {
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;
Expand All @@ -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 ??= {};

Expand All @@ -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", {
Expand Down
21 changes: 7 additions & 14 deletions packages/opencode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -144,23 +143,18 @@ const opencodeToolCreators: Record<string, OpenCodeToolCreator> = {
});
},
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: {
command: tool.schema.string().describe("Command name to execute, without the leading slash"),
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: {
Expand Down Expand Up @@ -269,8 +263,7 @@ export async function createOpenCodeTools(
client: PluginInput["client"],
projectRoot: string,
): Promise<Record<string, ToolDefinition>> {
const userConfig = await loadKompassConfig(projectRoot);
const config = mergeWithDefaults(userConfig);
const config = await loadMergedKompassConfig(projectRoot);
const tools: Record<string, ToolDefinition> = {};
const logger = createPluginLogger(client, projectRoot);

Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading