Add an opt-in OpenCode plugin that replaces the built-in provider behavioral prompt with a minimal identity while preserving environment, project, MCP, skill, history, and tool context. Track the active agent per session and apply the transform only to build and plan. Keep plan/build mode reminders and permission enforcement owned by OpenCode, leave all other agents untouched, and fail safely when the expected prompt boundary is absent. Expose the feature in Behavior settings with localized guidance, explicit Save + Reload application, settings search integration, persisted boolean validation, and managed-runtime lifecycle composition that does not load the plugin while disabled or on external OpenCode servers. Document the runtime contract and cover plugin materialization, config preservation, build/plan selection, agent switching, unknown prompt formats, and settings sanitization.
69 lines
2.8 KiB
JavaScript
69 lines
2.8 KiB
JavaScript
import { parse as parseJsonc } from 'jsonc-parser';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
const PROVIDER_PROMPT_BOUNDARY = 'You are powered by the model named';
|
|
const MINIMAL_IDENTITY = 'You are OpenCode, a coding agent.';
|
|
|
|
const createPluginSource = () => String.raw`
|
|
const PROVIDER_PROMPT_BOUNDARY = ${JSON.stringify(PROVIDER_PROMPT_BOUNDARY)}
|
|
const MINIMAL_IDENTITY = ${JSON.stringify(MINIMAL_IDENTITY)}
|
|
const optimizedSessions = new Map()
|
|
|
|
export const OpenChamberSystemPromptPlugin = async () => ({
|
|
"chat.message": async (input, output) => {
|
|
if (!input.sessionID) return
|
|
const agent = output?.message?.agent ?? input.agent
|
|
if (agent === "build" || agent === "plan") {
|
|
optimizedSessions.set(input.sessionID, agent)
|
|
return
|
|
}
|
|
optimizedSessions.delete(input.sessionID)
|
|
},
|
|
event: async ({ event }) => {
|
|
if (event?.type === "session.deleted") optimizedSessions.delete(event.properties?.info?.id)
|
|
},
|
|
"experimental.chat.system.transform": async (input, output) => {
|
|
if (!input.sessionID || !optimizedSessions.has(input.sessionID)) return
|
|
const prompt = output.system.join("\n")
|
|
const boundary = prompt.indexOf(PROVIDER_PROMPT_BOUNDARY)
|
|
if (boundary < 0) return
|
|
output.system.length = 0
|
|
output.system.push(MINIMAL_IDENTITY + "\n\n" + prompt.slice(boundary))
|
|
},
|
|
})
|
|
`;
|
|
|
|
const mergePluginConfig = (rawConfig, pluginUrl) => {
|
|
const errors = [];
|
|
const parsed = typeof rawConfig === 'string' && rawConfig.trim()
|
|
? parseJsonc(rawConfig, errors, { allowTrailingComma: true })
|
|
: {};
|
|
if (errors.length > 0 || !parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
throw new Error('OPENCODE_CONFIG_CONTENT must contain a valid JSON object before OpenChamber can inject its system prompt optimizer');
|
|
}
|
|
if (parsed.plugin !== undefined && !Array.isArray(parsed.plugin)) {
|
|
throw new Error('OPENCODE_CONFIG_CONTENT plugin must be an array before OpenChamber can inject its system prompt optimizer');
|
|
}
|
|
const configured = Array.isArray(parsed.plugin) ? parsed.plugin : [];
|
|
parsed.plugin = [
|
|
...configured.filter((value) => value !== pluginUrl && (!Array.isArray(value) || value[0] !== pluginUrl)),
|
|
pluginUrl,
|
|
];
|
|
return JSON.stringify(parsed);
|
|
};
|
|
|
|
export const createSystemPromptRuntime = ({ fsPromises, path, dataDir }) => {
|
|
const pluginDirectory = path.join(dataDir, 'system-prompt');
|
|
const pluginPath = path.join(pluginDirectory, 'openchamber-system-prompt-plugin.js');
|
|
|
|
const prepareManagedOpenCodeEnv = async (rawConfig) => {
|
|
await fsPromises.mkdir(pluginDirectory, { recursive: true });
|
|
await fsPromises.writeFile(pluginPath, createPluginSource(), { mode: 0o600 });
|
|
return {
|
|
OPENCODE_CONFIG_CONTENT: mergePluginConfig(rawConfig, pathToFileURL(pluginPath).href),
|
|
};
|
|
};
|
|
|
|
return { prepareManagedOpenCodeEnv };
|
|
};
|