Parent directory

opencode.ts

4493 bytes
  1import type { createOpencodeClient, Part } from "@opencode-ai/sdk";
  2import { readFileSync, writeFileSync } from "fs";
  3import { resolve } from "path";
  4
  5type Client = ReturnType<typeof createOpencodeClient>;
  6
  7let client: Client;
  8let sessionsFile: string;
  9
 10const chatSessions = new Map<number, string>();
 11const chatAgents = new Map<number, string>();
 12
 13// When set, all Telegram messages route to this session (interactive mode).
 14let attachedSession: string | null = null;
 15
 16export function setAttachedSession(sessionId: string): void {
 17  attachedSession = sessionId;
 18}
 19
 20export function clearAttachedSession(): void {
 21  attachedSession = null;
 22}
 23
 24export function init(c: Client, directory: string): void {
 25  client = c;
 26  sessionsFile = resolve(directory, ".opencode", "sessions.json");
 27  loadSessions();
 28}
 29
 30function loadSessions(): void {
 31  try {
 32    const raw = readFileSync(sessionsFile, "utf8");
 33    const obj = JSON.parse(raw) as Record<string, string>;
 34    for (const [k, v] of Object.entries(obj)) chatSessions.set(Number(k), v);
 35  } catch {
 36    // no file or invalid — start fresh
 37  }
 38}
 39
 40function saveSessions(): void {
 41  const obj: Record<string, string> = {};
 42  for (const [k, v] of chatSessions) obj[String(k)] = v;
 43  writeFileSync(sessionsFile, JSON.stringify(obj, null, 2));
 44}
 45
 46export async function getOrCreateSession(
 47  chatId: number,
 48): Promise<{ sessionId: string; fallback: boolean }> {
 49  const target = attachedSession ?? chatSessions.get(chatId);
 50  if (target) {
 51    const list = await client.session.list();
 52    if (!list.error && list.data?.some((s) => s.id === target)) {
 53      return { sessionId: target, fallback: false };
 54    }
 55    // Attached session gone — clear and fall through
 56    if (attachedSession) attachedSession = null;
 57  }
 58
 59  const result = await client.session.create();
 60  if (result.error)
 61    throw new Error(`failed to create session: ${result.error}`);
 62
 63  const sessionId = result.data.id;
 64  chatSessions.set(chatId, sessionId);
 65  saveSessions();
 66  return { sessionId, fallback: target !== undefined };
 67}
 68
 69export async function listAgents(): Promise<string[]> {
 70  const result = await client.app.agents();
 71  if (result.error)
 72    throw new Error(`list agents failed: ${JSON.stringify(result.error)}`);
 73  if (!result.data) return [];
 74  return result.data.map((a) => a.name);
 75}
 76
 77export function getAgent(chatId: number): string | undefined {
 78  return chatAgents.get(chatId);
 79}
 80
 81export function setAgent(chatId: number, agent: string): void {
 82  chatAgents.set(chatId, agent);
 83}
 84
 85export async function sendPrompt(
 86  sessionId: string,
 87  text: string,
 88  agent?: string,
 89): Promise<Part[]> {
 90  const result = await client.session.prompt({
 91    path: { id: sessionId },
 92    body: { parts: [{ type: "text", text }], ...(agent ? { agent } : {}) },
 93  });
 94  if (result.error)
 95    throw new Error(`prompt failed: ${JSON.stringify(result.error)}`);
 96
 97  return result.data.parts;
 98}
 99
100export async function createNewSession(chatId: number): Promise<string> {
101  const result = await client.session.create();
102  if (result.error)
103    throw new Error(`failed to create session: ${result.error}`);
104
105  const sessionId = result.data.id;
106  chatSessions.set(chatId, sessionId);
107  saveSessions();
108  return sessionId;
109}
110
111export async function listSessions(): Promise<
112  Array<{ id: string; title: string; created: number }>
113> {
114  const result = await client.session.list();
115  if (result.error) throw new Error(`list sessions failed: ${result.error}`);
116  if (!result.data) return [];
117
118  return result.data.map((s) => ({
119    id: s.id,
120    title: s.title,
121    created: s.time.created,
122  }));
123}
124
125export function getSessionId(chatId: number): string | undefined {
126  return chatSessions.get(chatId);
127}
128
129export function switchSession(chatId: number, sessionId: string): void {
130  chatSessions.set(chatId, sessionId);
131  saveSessions();
132}
133
134export async function abortSession(sessionId: string): Promise<void> {
135  const result = await client.session.abort({
136    path: { id: sessionId },
137  });
138  if (result.error)
139    throw new Error(`abort failed: ${JSON.stringify(result.error)}`);
140}
141
142export async function replyPermission(
143  sessionId: string,
144  permissionId: string,
145  response: "once" | "always" | "reject",
146): Promise<void> {
147  const result = await client.postSessionIdPermissionsPermissionId({
148    path: { id: sessionId, permissionID: permissionId },
149    body: { response },
150  });
151  if (result.error)
152    throw new Error(`permission reply failed: ${JSON.stringify(result.error)}`);
153}