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}