feat: wire OpenCode as selectable agent backend via AGENT_BACKEND env var
Some checks failed
Update token count / update-tokens (push) Has been cancelled
Some checks failed
Update token count / update-tokens (push) Has been cancelled
- Add AGENT_BACKEND config ('container' default, 'opencode' to use OpenCode runtime)
- Wire OpenCodeRuntime into runAgent() with fallback to container runner
- Skip container system check when using OpenCode backend
- Update README and CLAUDE.md with AGENT_BACKEND docs
This commit is contained in:
133
src/index.ts
133
src/index.ts
@@ -3,6 +3,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {
|
||||
AGENT_BACKEND,
|
||||
ASSISTANT_NAME,
|
||||
DATA_DIR,
|
||||
DISCORD_BOT_TOKEN,
|
||||
@@ -16,7 +17,107 @@ import { DiscordChannel } from './channels/discord.js';
|
||||
import { WhatsAppChannel } from './channels/whatsapp.js';
|
||||
import {
|
||||
ContainerOutput,
|
||||
runContainerAgent,
|
||||
async function runAgent(
|
||||
group: RegisteredGroup,
|
||||
prompt: string,
|
||||
chatJid: string,
|
||||
onOutput?: (output: ContainerOutput) => Promise<void>,
|
||||
): Promise<'success' | 'error'> {
|
||||
// --- OpenCode backend ---
|
||||
if (opencode) {
|
||||
try {
|
||||
const response = await opencode.chat(prompt, chatJid);
|
||||
if (response.error) {
|
||||
logger.error({ group: group.name, error: response.error }, 'OpenCode agent error');
|
||||
if (onOutput) {
|
||||
await onOutput({ status: 'error', error: response.error, result: null, newSessionId: response.sessionId });
|
||||
}
|
||||
return 'error';
|
||||
}
|
||||
if (onOutput && response.text) {
|
||||
await onOutput({ status: 'success', result: response.text, error: null, newSessionId: response.sessionId });
|
||||
}
|
||||
return 'success';
|
||||
} catch (err) {
|
||||
logger.error({ group: group.name, err }, 'OpenCode agent error');
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Container backend (Claude Agent SDK) ---
|
||||
const isMain = group.folder === MAIN_GROUP_FOLDER;
|
||||
const sessionId = sessions[group.folder];
|
||||
|
||||
// Update tasks snapshot for container to read (filtered by group)
|
||||
const tasks = getAllTasks();
|
||||
writeTasksSnapshot(
|
||||
group.folder,
|
||||
isMain,
|
||||
tasks.map((t) => ({
|
||||
id: t.id,
|
||||
groupFolder: t.group_folder,
|
||||
prompt: t.prompt,
|
||||
schedule_type: t.schedule_type,
|
||||
schedule_value: t.schedule_value,
|
||||
status: t.status,
|
||||
next_run: t.next_run,
|
||||
})),
|
||||
);
|
||||
|
||||
// Update available groups snapshot (main group only can see all groups)
|
||||
const availableGroups = getAvailableGroups();
|
||||
writeGroupsSnapshot(
|
||||
group.folder,
|
||||
isMain,
|
||||
availableGroups,
|
||||
new Set(Object.keys(registeredGroups)),
|
||||
);
|
||||
|
||||
// Wrap onOutput to track session ID from streamed results
|
||||
const wrappedOnOutput = onOutput
|
||||
? async (output: ContainerOutput) => {
|
||||
if (output.newSessionId) {
|
||||
sessions[group.folder] = output.newSessionId;
|
||||
setSession(group.folder, output.newSessionId);
|
||||
}
|
||||
await onOutput(output);
|
||||
}
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const output = await runContainerAgent(
|
||||
group,
|
||||
{
|
||||
prompt,
|
||||
sessionId,
|
||||
groupFolder: group.folder,
|
||||
chatJid,
|
||||
isMain,
|
||||
},
|
||||
(proc, containerName) => queue.registerProcess(chatJid, proc, containerName, group.folder),
|
||||
wrappedOnOutput,
|
||||
);
|
||||
|
||||
if (output.newSessionId) {
|
||||
sessions[group.folder] = output.newSessionId;
|
||||
setSession(group.folder, output.newSessionId);
|
||||
}
|
||||
|
||||
if (output.status === 'error') {
|
||||
logger.error(
|
||||
{ group: group.name, error: output.error },
|
||||
'Container agent error',
|
||||
);
|
||||
return 'error';
|
||||
}
|
||||
|
||||
return 'success';
|
||||
} catch (err) {
|
||||
logger.error({ group: group.name, err }, 'Agent error');
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
,
|
||||
writeGroupsSnapshot,
|
||||
writeTasksSnapshot,
|
||||
} from './container-runner.js';
|
||||
@@ -41,6 +142,7 @@ import { findChannel, formatMessages, formatOutbound } from './router.js';
|
||||
import { startSchedulerLoop } from './task-scheduler.js';
|
||||
import { Channel, NewMessage, RegisteredGroup } from './types.js';
|
||||
import { logger } from './logger.js';
|
||||
import { OpenCodeRuntime } from './opencode/runtime.js';
|
||||
|
||||
// Re-export for backwards compatibility during refactor
|
||||
export { escapeXml, formatMessages } from './router.js';
|
||||
@@ -54,6 +156,7 @@ let messageLoopRunning = false;
|
||||
let whatsapp: WhatsAppChannel;
|
||||
const channels: Channel[] = [];
|
||||
const queue = new GroupQueue();
|
||||
const opencode = AGENT_BACKEND === 'opencode' ? new OpenCodeRuntime() : null;
|
||||
|
||||
function loadState(): void {
|
||||
lastTimestamp = getRouterState('last_timestamp') || '';
|
||||
@@ -221,6 +324,28 @@ async function runAgent(
|
||||
chatJid: string,
|
||||
onOutput?: (output: ContainerOutput) => Promise<void>,
|
||||
): Promise<'success' | 'error'> {
|
||||
// --- OpenCode backend ---
|
||||
if (opencode) {
|
||||
try {
|
||||
const response = await opencode.chat(prompt, chatJid);
|
||||
if (response.error) {
|
||||
logger.error({ group: group.name, error: response.error }, 'OpenCode agent error');
|
||||
if (onOutput) {
|
||||
await onOutput({ status: 'error', error: response.error, result: null, newSessionId: response.sessionId });
|
||||
}
|
||||
return 'error';
|
||||
}
|
||||
if (onOutput && response.text) {
|
||||
await onOutput({ status: 'success', result: response.text, error: null, newSessionId: response.sessionId });
|
||||
}
|
||||
return 'success';
|
||||
} catch (err) {
|
||||
logger.error({ group: group.name, err }, 'OpenCode agent error');
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Container backend (Claude Agent SDK) ---
|
||||
const isMain = group.folder === MAIN_GROUP_FOLDER;
|
||||
const sessionId = sessions[group.folder];
|
||||
|
||||
@@ -464,7 +589,11 @@ function ensureContainerSystemRunning(): void {
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
ensureContainerSystemRunning();
|
||||
if (AGENT_BACKEND === 'container') {
|
||||
ensureContainerSystemRunning();
|
||||
} else {
|
||||
logger.info({ backend: AGENT_BACKEND }, 'Using OpenCode agent backend, skipping container system check');
|
||||
}
|
||||
initDatabase();
|
||||
logger.info('Database initialized');
|
||||
loadState();
|
||||
|
||||
Reference in New Issue
Block a user