opencode-tbot 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/README.zh-CN.md +3 -3
- package/dist/cli.js +127 -17
- package/dist/cli.js.map +1 -1
- package/dist/plugin.js +9 -4
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ A Telegram plugin for driving [OpenCode](https://opencode.ai) from chat.
|
|
|
22
22
|
Run:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm
|
|
25
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- install
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
The installer registers the plugin globally and writes the default runtime config.
|
|
@@ -30,16 +30,16 @@ The installer registers the plugin globally and writes the default runtime confi
|
|
|
30
30
|
Check the installed CLI version:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
npm exec opencode-tbot -- --version
|
|
33
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- --version
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Update the registered npm plugin spec in OpenCode:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npm exec opencode-tbot -- update
|
|
39
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Remove
|
|
42
|
+
Remove a legacy local npm install if OpenCode is showing the plugin as a `file:///.../node_modules/...` path:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
npm uninstall opencode-tbot
|
package/README.zh-CN.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
执行:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm
|
|
25
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- install
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
安装器会注册全局插件并写入默认运行时配置。
|
|
@@ -30,13 +30,13 @@ npm install opencode-tbot@latest
|
|
|
30
30
|
查看已安装的 CLI 版本:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
npm exec opencode-tbot -- --version
|
|
33
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- --version
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
更新 OpenCode 中注册的 npm 插件 spec:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npm exec opencode-tbot -- update
|
|
39
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
从当前环境卸载 npm 包:
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { a as writePluginConfigFile, n as getOpenCodeConfigFilePath, o as OPENCODE_TBOT_VERSION, r as mergePluginConfigSources, t as getGlobalPluginConfigFilePath } from "./assets/plugin-config-CGIe9zdA.js";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { dirname } from "node:path";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { stderr, stdin, stdout } from "node:process";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
import { parse } from "jsonc-parser";
|
|
8
8
|
//#region src/cli.ts
|
|
9
9
|
var DEFAULT_PLUGIN_SPEC = "opencode-tbot@latest";
|
|
10
|
+
var PROMPT_CANCELLED_ERROR = "Prompt cancelled.";
|
|
10
11
|
async function main(argv = process.argv.slice(2)) {
|
|
11
12
|
try {
|
|
12
13
|
const exitCode = await runCli(argv);
|
|
@@ -43,8 +44,8 @@ async function installPlugin(options = {}) {
|
|
|
43
44
|
const existingPluginConfig = await readPluginConfigFile(globalPluginConfigFilePath);
|
|
44
45
|
const prompt = createPromptSession();
|
|
45
46
|
try {
|
|
46
|
-
const botToken = normalizeRequiredString(options.botToken ?? await prompt.
|
|
47
|
-
const openrouterApiKey = options.enableVoice ?? await prompt.confirm("Enable voice transcription? (y/N): ", false) ? normalizeRequiredString(options.openrouterApiKey ?? await prompt.
|
|
47
|
+
const botToken = normalizeRequiredString(options.botToken ?? await prompt.askSecret("Telegram bot token: "), "Telegram bot token is required.");
|
|
48
|
+
const openrouterApiKey = options.enableVoice ?? await prompt.confirm("Enable voice transcription? (y/N): ", false) ? normalizeRequiredString(options.openrouterApiKey ?? await prompt.askSecret("OpenRouter API key: "), "OpenRouter API key is required when voice transcription is enabled.") : null;
|
|
48
49
|
const telegramApiRoot = normalizeOptionalString(options.telegramApiRoot) ?? "https://api.telegram.org";
|
|
49
50
|
const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;
|
|
50
51
|
const nextOpenCodeConfig = options.registerPlugin === false ? openCodeConfig : ensurePluginRegistered(openCodeConfig, pluginSpec);
|
|
@@ -55,16 +56,19 @@ async function installPlugin(options = {}) {
|
|
|
55
56
|
if (options.registerPlugin === false) stdout.write(`Skipped plugin registration in ${openCodeConfigFilePath}\n`);
|
|
56
57
|
else stdout.write(`Registered ${pluginSpec} in ${openCodeConfigFilePath}\n`);
|
|
57
58
|
stdout.write(`Wrote plugin defaults to ${globalPluginConfigFilePath}\n`);
|
|
59
|
+
await warnAboutLegacyLocalInstallations(homeDir);
|
|
58
60
|
} finally {
|
|
59
61
|
prompt.close();
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
async function updatePlugin(options = {}) {
|
|
63
|
-
const
|
|
65
|
+
const homeDir = options.homeDir ?? homedir();
|
|
66
|
+
const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);
|
|
64
67
|
const openCodeConfig = await readJsoncObject(openCodeConfigFilePath);
|
|
65
68
|
const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;
|
|
66
69
|
await writeJsonFile(openCodeConfigFilePath, replacePluginRegistration(openCodeConfig, pluginSpec));
|
|
67
70
|
stdout.write(`Updated plugin registration to ${pluginSpec} in ${openCodeConfigFilePath}\n`);
|
|
71
|
+
await warnAboutLegacyLocalInstallations(homeDir);
|
|
68
72
|
}
|
|
69
73
|
function parseCliOptions(argv) {
|
|
70
74
|
const args = [...argv];
|
|
@@ -186,34 +190,108 @@ async function writeJsonFile(filePath, value) {
|
|
|
186
190
|
async function ensureParentDirectory(filePath) {
|
|
187
191
|
await mkdir(dirname(filePath), { recursive: true });
|
|
188
192
|
}
|
|
189
|
-
function createPromptSession() {
|
|
190
|
-
|
|
193
|
+
function createPromptSession(options = {}) {
|
|
194
|
+
const input = options.input ?? stdin;
|
|
195
|
+
const output = options.output ?? stdout;
|
|
196
|
+
if (!input.isTTY || !output.isTTY) return {
|
|
191
197
|
ask: async () => "",
|
|
198
|
+
askSecret: async () => "",
|
|
192
199
|
async confirm(_question, defaultValue) {
|
|
193
200
|
return defaultValue;
|
|
194
201
|
},
|
|
195
202
|
close() {}
|
|
196
203
|
};
|
|
197
|
-
const readline = createInterface({
|
|
198
|
-
input: stdin,
|
|
199
|
-
output: stdout
|
|
200
|
-
});
|
|
201
204
|
return {
|
|
202
205
|
ask(question) {
|
|
203
|
-
return
|
|
206
|
+
return askQuestion(input, output, question);
|
|
207
|
+
},
|
|
208
|
+
askSecret(question) {
|
|
209
|
+
return askSecretQuestion(input, output, question);
|
|
204
210
|
},
|
|
205
211
|
async confirm(question, defaultValue) {
|
|
206
|
-
const answer = normalizeOptionalString(await
|
|
212
|
+
const answer = normalizeOptionalString(await askQuestion(input, output, question));
|
|
207
213
|
if (!answer) return defaultValue;
|
|
208
214
|
if (["y", "yes"].includes(answer.toLowerCase())) return true;
|
|
209
215
|
if (["n", "no"].includes(answer.toLowerCase())) return false;
|
|
210
216
|
throw new Error(`Unsupported answer: ${answer}`);
|
|
211
217
|
},
|
|
212
|
-
close() {
|
|
213
|
-
readline.close();
|
|
214
|
-
}
|
|
218
|
+
close() {}
|
|
215
219
|
};
|
|
216
220
|
}
|
|
221
|
+
async function askQuestion(input, output, question) {
|
|
222
|
+
const readline = createInterface({
|
|
223
|
+
input,
|
|
224
|
+
output
|
|
225
|
+
});
|
|
226
|
+
try {
|
|
227
|
+
return await readline.question(question);
|
|
228
|
+
} finally {
|
|
229
|
+
readline.close();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function askSecretQuestion(input, output, question) {
|
|
233
|
+
if (typeof input.setRawMode !== "function") return askQuestion(input, output, question);
|
|
234
|
+
output.write(question);
|
|
235
|
+
try {
|
|
236
|
+
return await readMaskedInput(input, output);
|
|
237
|
+
} finally {
|
|
238
|
+
output.write("\n");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function readMaskedInput(input, output) {
|
|
242
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
243
|
+
const buffer = [];
|
|
244
|
+
let settled = false;
|
|
245
|
+
const cleanup = () => {
|
|
246
|
+
input.off("data", handleData);
|
|
247
|
+
input.off("error", handleError);
|
|
248
|
+
input.pause();
|
|
249
|
+
input.setRawMode?.(false);
|
|
250
|
+
};
|
|
251
|
+
const rejectWith = (error) => {
|
|
252
|
+
if (settled) return;
|
|
253
|
+
settled = true;
|
|
254
|
+
cleanup();
|
|
255
|
+
rejectPromise(error);
|
|
256
|
+
};
|
|
257
|
+
const resolveWith = (value) => {
|
|
258
|
+
if (settled) return;
|
|
259
|
+
settled = true;
|
|
260
|
+
cleanup();
|
|
261
|
+
resolvePromise(value);
|
|
262
|
+
};
|
|
263
|
+
const handleError = (error) => {
|
|
264
|
+
rejectWith(error);
|
|
265
|
+
};
|
|
266
|
+
const handleData = (chunk) => {
|
|
267
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
|
|
268
|
+
for (const character of text) {
|
|
269
|
+
if (character === "\r" || character === "\n") {
|
|
270
|
+
resolveWith(buffer.join(""));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (character === "") {
|
|
274
|
+
rejectWith(new Error(PROMPT_CANCELLED_ERROR));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (character === "\b" || character === "") {
|
|
278
|
+
if (buffer.length > 0) {
|
|
279
|
+
buffer.pop();
|
|
280
|
+
output.write("\b \b");
|
|
281
|
+
}
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (character < " " || character === "") continue;
|
|
285
|
+
buffer.push(character);
|
|
286
|
+
output.write("*");
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
input.setRawMode?.(true);
|
|
290
|
+
input.resume();
|
|
291
|
+
input.on("error", handleError);
|
|
292
|
+
input.on("data", handleData);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
217
295
|
function normalizeOptionalString(value) {
|
|
218
296
|
const normalized = value?.trim();
|
|
219
297
|
return normalized ? normalized : null;
|
|
@@ -233,11 +311,43 @@ function isOpencodeTbotPluginSpec(value) {
|
|
|
233
311
|
const normalized = value.trim();
|
|
234
312
|
return normalized === "opencode-tbot" || normalized.startsWith("opencode-tbot@");
|
|
235
313
|
}
|
|
314
|
+
async function warnAboutLegacyLocalInstallations(homeDir) {
|
|
315
|
+
const legacyInstallations = await findLegacyLocalInstallations(homeDir);
|
|
316
|
+
if (legacyInstallations.length === 0) return;
|
|
317
|
+
stdout.write("\nDetected local opencode-tbot npm installation(s) that can make OpenCode show the plugin as file:///.../node_modules/...\n");
|
|
318
|
+
for (const installation of legacyInstallations) {
|
|
319
|
+
stdout.write(`- ${installation.packagePath}\n`);
|
|
320
|
+
stdout.write(` cleanup: cd "${installation.rootDir}" && npm uninstall opencode-tbot\n`);
|
|
321
|
+
}
|
|
322
|
+
stdout.write("Recommended npm flow: npm exec --package opencode-tbot@latest opencode-tbot -- install\n");
|
|
323
|
+
}
|
|
324
|
+
async function findLegacyLocalInstallations(homeDir) {
|
|
325
|
+
const roots = [...new Set([resolve(process.cwd()), resolve(homeDir)])];
|
|
326
|
+
const installations = [];
|
|
327
|
+
for (const rootDir of roots) if (await pathExists(join(rootDir, "node_modules", "opencode-tbot", "package.json"))) installations.push({
|
|
328
|
+
packagePath: join(rootDir, "node_modules", "opencode-tbot"),
|
|
329
|
+
rootDir
|
|
330
|
+
});
|
|
331
|
+
return installations;
|
|
332
|
+
}
|
|
333
|
+
async function pathExists(filePath) {
|
|
334
|
+
try {
|
|
335
|
+
await stat(filePath);
|
|
336
|
+
return true;
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
236
341
|
function buildHelpText() {
|
|
237
342
|
return [
|
|
238
343
|
"Usage: opencode-tbot [install|update] [options]",
|
|
239
344
|
" opencode-tbot --version",
|
|
240
345
|
"",
|
|
346
|
+
"Recommended npm usage:",
|
|
347
|
+
" npm exec --package opencode-tbot@latest opencode-tbot -- install",
|
|
348
|
+
" npm exec --package opencode-tbot@latest opencode-tbot -- update",
|
|
349
|
+
" npm exec --package opencode-tbot@latest opencode-tbot -- --version",
|
|
350
|
+
"",
|
|
241
351
|
"Commands:",
|
|
242
352
|
" install",
|
|
243
353
|
" update",
|
|
@@ -259,6 +369,6 @@ function formatCliError(error) {
|
|
|
259
369
|
return error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : String(error);
|
|
260
370
|
}
|
|
261
371
|
//#endregion
|
|
262
|
-
export { installPlugin, main, runCli, updatePlugin };
|
|
372
|
+
export { createPromptSession, installPlugin, main, runCli, updatePlugin };
|
|
263
373
|
|
|
264
374
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname } from \"node:path\";\nimport { stderr, stdin, stdout } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\nimport { parse } from \"jsonc-parser\";\nimport {\n DEFAULT_TELEGRAM_API_ROOT,\n type PluginConfigSource,\n} from \"./app/config.js\";\nimport { OPENCODE_TBOT_VERSION } from \"./app/package-info.js\";\nimport {\n getGlobalPluginConfigFilePath,\n getOpenCodeConfigFilePath,\n mergePluginConfigSources,\n writePluginConfigFile,\n} from \"./app/plugin-config.js\";\n\nexport interface InstallCommandOptions {\n botToken?: string;\n enableVoice?: boolean;\n homeDir?: string;\n openrouterApiKey?: string;\n pluginSpec?: string;\n registerPlugin?: boolean;\n telegramApiRoot?: string;\n}\n\ninterface CliOptions extends InstallCommandOptions {\n command: \"help\" | \"install\" | \"update\" | \"version\";\n}\n\ninterface OpenCodeGlobalConfig {\n plugin?: string[];\n [key: string]: unknown;\n}\n\nconst DEFAULT_PLUGIN_SPEC = \"opencode-tbot@latest\";\n\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<number> {\n try {\n const exitCode = await runCli(argv);\n process.exitCode = exitCode;\n\n return exitCode;\n } catch (error) {\n stderr.write(`${formatCliError(error)}\\n`);\n process.exitCode = 1;\n\n return 1;\n }\n}\n\nexport async function runCli(argv: string[]): Promise<number> {\n const options = parseCliOptions(argv);\n\n if (options.command === \"help\") {\n stdout.write(`${buildHelpText()}\\n`);\n return 0;\n }\n\n if (options.command === \"version\") {\n stdout.write(`${OPENCODE_TBOT_VERSION}\\n`);\n return 0;\n }\n\n if (options.command === \"update\") {\n await updatePlugin(options);\n return 0;\n }\n\n await installPlugin(options);\n return 0;\n}\n\nexport async function installPlugin(options: InstallCommandOptions = {}): Promise<void> {\n const homeDir = options.homeDir ?? homedir();\n const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);\n const globalPluginConfigFilePath = getGlobalPluginConfigFilePath(homeDir);\n const openCodeConfig = await readJsoncObject<OpenCodeGlobalConfig>(openCodeConfigFilePath);\n const existingPluginConfig = await readPluginConfigFile(globalPluginConfigFilePath);\n const prompt = createPromptSession();\n\n try {\n const botToken = normalizeRequiredString(\n options.botToken ?? await prompt.ask(\"Telegram bot token: \"),\n \"Telegram bot token is required.\",\n );\n const enableVoice = options.enableVoice ?? await prompt.confirm(\n \"Enable voice transcription? (y/N): \",\n false,\n );\n const openrouterApiKey = enableVoice\n ? normalizeRequiredString(\n options.openrouterApiKey ?? await prompt.ask(\"OpenRouter API key: \"),\n \"OpenRouter API key is required when voice transcription is enabled.\",\n )\n : null;\n const telegramApiRoot = normalizeOptionalString(options.telegramApiRoot) ?? DEFAULT_TELEGRAM_API_ROOT;\n const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;\n const nextOpenCodeConfig = options.registerPlugin === false\n ? openCodeConfig\n : ensurePluginRegistered(openCodeConfig, pluginSpec);\n const nextPluginConfig = buildInstalledPluginConfig(\n existingPluginConfig,\n botToken,\n telegramApiRoot,\n openrouterApiKey,\n );\n\n if (options.registerPlugin === false) {\n await ensureParentDirectory(openCodeConfigFilePath);\n } else {\n await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);\n }\n await writePluginConfigFile(globalPluginConfigFilePath, nextPluginConfig);\n\n if (options.registerPlugin === false) {\n stdout.write(`Skipped plugin registration in ${openCodeConfigFilePath}\\n`);\n } else {\n stdout.write(`Registered ${pluginSpec} in ${openCodeConfigFilePath}\\n`);\n }\n stdout.write(`Wrote plugin defaults to ${globalPluginConfigFilePath}\\n`);\n } finally {\n prompt.close();\n }\n}\n\nexport async function updatePlugin(options: Pick<InstallCommandOptions, \"homeDir\" | \"pluginSpec\"> = {}): Promise<void> {\n const homeDir = options.homeDir ?? homedir();\n const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);\n const openCodeConfig = await readJsoncObject<OpenCodeGlobalConfig>(openCodeConfigFilePath);\n const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;\n const nextOpenCodeConfig = replacePluginRegistration(openCodeConfig, pluginSpec);\n\n await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);\n stdout.write(`Updated plugin registration to ${pluginSpec} in ${openCodeConfigFilePath}\\n`);\n}\n\nfunction parseCliOptions(argv: string[]): CliOptions {\n const args = [...argv];\n const first = args[0];\n const command = !first || first.startsWith(\"-\")\n ? \"install\"\n : first;\n const values = command === \"install\" || command === \"version\"\n ? args\n : args.slice(1);\n const options: CliOptions = {\n command: command === \"help\" || command === \"--help\" || command === \"-h\"\n ? \"help\"\n : command === \"version\" || command === \"--version\" || command === \"-v\"\n ? \"version\"\n : command === \"update\"\n ? \"update\"\n : \"install\",\n };\n\n for (let index = 0; index < values.length; index += 1) {\n const value = values[index];\n\n if (index === 0 && !value.startsWith(\"-\")) {\n continue;\n }\n\n switch (value) {\n case \"--bot-token\":\n options.botToken = values[++index];\n break;\n case \"--enable-voice\":\n options.enableVoice = true;\n break;\n case \"--disable-voice\":\n options.enableVoice = false;\n break;\n case \"--openrouter-api-key\":\n options.openrouterApiKey = values[++index];\n break;\n case \"--plugin-spec\":\n options.pluginSpec = values[++index];\n break;\n case \"--telegram-api-root\":\n options.telegramApiRoot = values[++index];\n break;\n case \"--skip-register\":\n options.registerPlugin = false;\n break;\n case \"--home-dir\":\n options.homeDir = values[++index];\n break;\n case \"--help\":\n case \"-h\":\n options.command = \"help\";\n break;\n case \"--version\":\n case \"-v\":\n options.command = \"version\";\n break;\n default:\n throw new Error(`Unknown argument: ${value}`);\n }\n }\n\n return options;\n}\n\nfunction buildInstalledPluginConfig(\n current: PluginConfigSource,\n botToken: string,\n telegramApiRoot: string,\n openrouterApiKey: string | null,\n): PluginConfigSource {\n const merged = mergePluginConfigSources(current, {\n telegram: {\n botToken,\n apiRoot: telegramApiRoot,\n },\n });\n const nextOpenRouter = openrouterApiKey\n ? {\n ...(merged.openrouter ?? {}),\n apiKey: openrouterApiKey,\n }\n : removeOpenRouterApiKey(merged.openrouter);\n\n return {\n ...merged,\n ...(nextOpenRouter && Object.keys(nextOpenRouter).length > 0\n ? { openrouter: nextOpenRouter }\n : {}),\n };\n}\n\nfunction removeOpenRouterApiKey(\n config: PluginConfigSource[\"openrouter\"],\n): PluginConfigSource[\"openrouter\"] | undefined {\n if (!config) {\n return undefined;\n }\n\n const { apiKey: _apiKey, ...rest } = config;\n\n return Object.keys(rest).length > 0 ? rest : undefined;\n}\n\nfunction ensurePluginRegistered(\n config: OpenCodeGlobalConfig,\n pluginSpec: string,\n): OpenCodeGlobalConfig {\n const plugins = Array.isArray(config.plugin)\n ? config.plugin.filter((item): item is string => typeof item === \"string\")\n : [];\n\n if (!plugins.includes(pluginSpec)) {\n plugins.push(pluginSpec);\n }\n\n return {\n ...config,\n plugin: plugins,\n };\n}\n\nfunction replacePluginRegistration(\n config: OpenCodeGlobalConfig,\n pluginSpec: string,\n): OpenCodeGlobalConfig {\n const currentPlugins = Array.isArray(config.plugin)\n ? config.plugin.filter((item): item is string => typeof item === \"string\")\n : [];\n const nextPlugins: string[] = [];\n let inserted = false;\n\n for (const currentPlugin of currentPlugins) {\n if (isOpencodeTbotPluginSpec(currentPlugin)) {\n if (!inserted) {\n nextPlugins.push(pluginSpec);\n inserted = true;\n }\n\n continue;\n }\n\n nextPlugins.push(currentPlugin);\n }\n\n if (!inserted) {\n nextPlugins.push(pluginSpec);\n }\n\n return {\n ...config,\n plugin: nextPlugins,\n };\n}\n\nasync function readPluginConfigFile(configFilePath: string): Promise<PluginConfigSource> {\n try {\n const content = await readFile(configFilePath, \"utf8\");\n const parsed = JSON.parse(content) as unknown;\n\n return isPlainObject(parsed)\n ? parsed as PluginConfigSource\n : {};\n } catch (error) {\n if (isMissingFileError(error)) {\n return {};\n }\n\n throw error;\n }\n}\n\nasync function readJsoncObject<TObject extends Record<string, unknown>>(\n filePath: string,\n): Promise<TObject> {\n try {\n const content = await readFile(filePath, \"utf8\");\n const parsed = parse(content) as unknown;\n\n return isPlainObject(parsed)\n ? parsed as TObject\n : {} as TObject;\n } catch (error) {\n if (isMissingFileError(error)) {\n return {} as TObject;\n }\n\n throw error;\n }\n}\n\nasync function writeJsonFile(filePath: string, value: Record<string, unknown>): Promise<void> {\n await ensureParentDirectory(filePath);\n await writeFile(filePath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nasync function ensureParentDirectory(filePath: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n}\n\nfunction createPromptSession() {\n if (!stdin.isTTY || !stdout.isTTY) {\n return {\n ask: async () => \"\",\n async confirm(_question: string, defaultValue: boolean) {\n return defaultValue;\n },\n close() { },\n };\n }\n\n const readline = createInterface({\n input: stdin,\n output: stdout,\n });\n\n return {\n ask(question: string) {\n return readline.question(question);\n },\n async confirm(question: string, defaultValue: boolean) {\n const answer = normalizeOptionalString(await readline.question(question));\n\n if (!answer) {\n return defaultValue;\n }\n\n if ([\"y\", \"yes\"].includes(answer.toLowerCase())) {\n return true;\n }\n\n if ([\"n\", \"no\"].includes(answer.toLowerCase())) {\n return false;\n }\n\n throw new Error(`Unsupported answer: ${answer}`);\n },\n close() {\n readline.close();\n },\n };\n}\n\nfunction normalizeOptionalString(value: string | undefined | null): string | null {\n const normalized = value?.trim();\n\n return normalized\n ? normalized\n : null;\n}\n\nfunction normalizeRequiredString(value: string | undefined | null, errorMessage: string): string {\n const normalized = normalizeOptionalString(value);\n\n if (!normalized) {\n throw new Error(errorMessage);\n }\n\n return normalized;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isMissingFileError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\nfunction isOpencodeTbotPluginSpec(value: string): boolean {\n const normalized = value.trim();\n\n return normalized === \"opencode-tbot\" || normalized.startsWith(\"opencode-tbot@\");\n}\n\nfunction buildHelpText(): string {\n return [\n \"Usage: opencode-tbot [install|update] [options]\",\n \" opencode-tbot --version\",\n \"\",\n \"Commands:\",\n \" install\",\n \" update\",\n \"\",\n \"Options:\",\n \" --bot-token <token>\",\n \" --enable-voice\",\n \" --disable-voice\",\n \" --openrouter-api-key <key>\",\n \" --telegram-api-root <url>\",\n \" --plugin-spec <spec>\",\n \" --skip-register\",\n \" --home-dir <path>\",\n \" --version\",\n \" --help\",\n ].join(\"\\n\");\n}\n\nfunction formatCliError(error: unknown): string {\n return error instanceof Error && error.message.trim().length > 0\n ? error.message.trim()\n : String(error);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { stderr, stdin, stdout } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\nimport { parse } from \"jsonc-parser\";\nimport {\n DEFAULT_TELEGRAM_API_ROOT,\n type PluginConfigSource,\n} from \"./app/config.js\";\nimport { OPENCODE_TBOT_VERSION } from \"./app/package-info.js\";\nimport {\n getGlobalPluginConfigFilePath,\n getOpenCodeConfigFilePath,\n mergePluginConfigSources,\n writePluginConfigFile,\n} from \"./app/plugin-config.js\";\n\nexport interface InstallCommandOptions {\n botToken?: string;\n enableVoice?: boolean;\n homeDir?: string;\n openrouterApiKey?: string;\n pluginSpec?: string;\n registerPlugin?: boolean;\n telegramApiRoot?: string;\n}\n\ninterface CliOptions extends InstallCommandOptions {\n command: \"help\" | \"install\" | \"update\" | \"version\";\n}\n\nexport interface PromptSessionLike {\n ask(question: string): Promise<string>;\n askSecret(question: string): Promise<string>;\n confirm(question: string, defaultValue: boolean): Promise<boolean>;\n close(): void;\n}\n\nexport interface CreatePromptSessionOptions {\n input?: NodeJS.ReadStream;\n output?: NodeJS.WriteStream;\n}\n\ninterface OpenCodeGlobalConfig {\n plugin?: string[];\n [key: string]: unknown;\n}\n\nconst DEFAULT_PLUGIN_SPEC = \"opencode-tbot@latest\";\nconst PROMPT_CANCELLED_ERROR = \"Prompt cancelled.\";\n\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<number> {\n try {\n const exitCode = await runCli(argv);\n process.exitCode = exitCode;\n\n return exitCode;\n } catch (error) {\n stderr.write(`${formatCliError(error)}\\n`);\n process.exitCode = 1;\n\n return 1;\n }\n}\n\nexport async function runCli(argv: string[]): Promise<number> {\n const options = parseCliOptions(argv);\n\n if (options.command === \"help\") {\n stdout.write(`${buildHelpText()}\\n`);\n return 0;\n }\n\n if (options.command === \"version\") {\n stdout.write(`${OPENCODE_TBOT_VERSION}\\n`);\n return 0;\n }\n\n if (options.command === \"update\") {\n await updatePlugin(options);\n return 0;\n }\n\n await installPlugin(options);\n return 0;\n}\n\nexport async function installPlugin(options: InstallCommandOptions = {}): Promise<void> {\n const homeDir = options.homeDir ?? homedir();\n const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);\n const globalPluginConfigFilePath = getGlobalPluginConfigFilePath(homeDir);\n const openCodeConfig = await readJsoncObject<OpenCodeGlobalConfig>(openCodeConfigFilePath);\n const existingPluginConfig = await readPluginConfigFile(globalPluginConfigFilePath);\n const prompt = createPromptSession();\n\n try {\n const botToken = normalizeRequiredString(\n options.botToken ?? await prompt.askSecret(\"Telegram bot token: \"),\n \"Telegram bot token is required.\",\n );\n const enableVoice = options.enableVoice ?? await prompt.confirm(\n \"Enable voice transcription? (y/N): \",\n false,\n );\n const openrouterApiKey = enableVoice\n ? normalizeRequiredString(\n options.openrouterApiKey ?? await prompt.askSecret(\"OpenRouter API key: \"),\n \"OpenRouter API key is required when voice transcription is enabled.\",\n )\n : null;\n const telegramApiRoot = normalizeOptionalString(options.telegramApiRoot) ?? DEFAULT_TELEGRAM_API_ROOT;\n const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;\n const nextOpenCodeConfig = options.registerPlugin === false\n ? openCodeConfig\n : ensurePluginRegistered(openCodeConfig, pluginSpec);\n const nextPluginConfig = buildInstalledPluginConfig(\n existingPluginConfig,\n botToken,\n telegramApiRoot,\n openrouterApiKey,\n );\n\n if (options.registerPlugin === false) {\n await ensureParentDirectory(openCodeConfigFilePath);\n } else {\n await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);\n }\n await writePluginConfigFile(globalPluginConfigFilePath, nextPluginConfig);\n\n if (options.registerPlugin === false) {\n stdout.write(`Skipped plugin registration in ${openCodeConfigFilePath}\\n`);\n } else {\n stdout.write(`Registered ${pluginSpec} in ${openCodeConfigFilePath}\\n`);\n }\n stdout.write(`Wrote plugin defaults to ${globalPluginConfigFilePath}\\n`);\n await warnAboutLegacyLocalInstallations(homeDir);\n } finally {\n prompt.close();\n }\n}\n\nexport async function updatePlugin(options: Pick<InstallCommandOptions, \"homeDir\" | \"pluginSpec\"> = {}): Promise<void> {\n const homeDir = options.homeDir ?? homedir();\n const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);\n const openCodeConfig = await readJsoncObject<OpenCodeGlobalConfig>(openCodeConfigFilePath);\n const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;\n const nextOpenCodeConfig = replacePluginRegistration(openCodeConfig, pluginSpec);\n\n await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);\n stdout.write(`Updated plugin registration to ${pluginSpec} in ${openCodeConfigFilePath}\\n`);\n await warnAboutLegacyLocalInstallations(homeDir);\n}\n\nfunction parseCliOptions(argv: string[]): CliOptions {\n const args = [...argv];\n const first = args[0];\n const command = !first || first.startsWith(\"-\")\n ? \"install\"\n : first;\n const values = command === \"install\" || command === \"version\"\n ? args\n : args.slice(1);\n const options: CliOptions = {\n command: command === \"help\" || command === \"--help\" || command === \"-h\"\n ? \"help\"\n : command === \"version\" || command === \"--version\" || command === \"-v\"\n ? \"version\"\n : command === \"update\"\n ? \"update\"\n : \"install\",\n };\n\n for (let index = 0; index < values.length; index += 1) {\n const value = values[index];\n\n if (index === 0 && !value.startsWith(\"-\")) {\n continue;\n }\n\n switch (value) {\n case \"--bot-token\":\n options.botToken = values[++index];\n break;\n case \"--enable-voice\":\n options.enableVoice = true;\n break;\n case \"--disable-voice\":\n options.enableVoice = false;\n break;\n case \"--openrouter-api-key\":\n options.openrouterApiKey = values[++index];\n break;\n case \"--plugin-spec\":\n options.pluginSpec = values[++index];\n break;\n case \"--telegram-api-root\":\n options.telegramApiRoot = values[++index];\n break;\n case \"--skip-register\":\n options.registerPlugin = false;\n break;\n case \"--home-dir\":\n options.homeDir = values[++index];\n break;\n case \"--help\":\n case \"-h\":\n options.command = \"help\";\n break;\n case \"--version\":\n case \"-v\":\n options.command = \"version\";\n break;\n default:\n throw new Error(`Unknown argument: ${value}`);\n }\n }\n\n return options;\n}\n\nfunction buildInstalledPluginConfig(\n current: PluginConfigSource,\n botToken: string,\n telegramApiRoot: string,\n openrouterApiKey: string | null,\n): PluginConfigSource {\n const merged = mergePluginConfigSources(current, {\n telegram: {\n botToken,\n apiRoot: telegramApiRoot,\n },\n });\n const nextOpenRouter = openrouterApiKey\n ? {\n ...(merged.openrouter ?? {}),\n apiKey: openrouterApiKey,\n }\n : removeOpenRouterApiKey(merged.openrouter);\n\n return {\n ...merged,\n ...(nextOpenRouter && Object.keys(nextOpenRouter).length > 0\n ? { openrouter: nextOpenRouter }\n : {}),\n };\n}\n\nfunction removeOpenRouterApiKey(\n config: PluginConfigSource[\"openrouter\"],\n): PluginConfigSource[\"openrouter\"] | undefined {\n if (!config) {\n return undefined;\n }\n\n const { apiKey: _apiKey, ...rest } = config;\n\n return Object.keys(rest).length > 0 ? rest : undefined;\n}\n\nfunction ensurePluginRegistered(\n config: OpenCodeGlobalConfig,\n pluginSpec: string,\n): OpenCodeGlobalConfig {\n const plugins = Array.isArray(config.plugin)\n ? config.plugin.filter((item): item is string => typeof item === \"string\")\n : [];\n\n if (!plugins.includes(pluginSpec)) {\n plugins.push(pluginSpec);\n }\n\n return {\n ...config,\n plugin: plugins,\n };\n}\n\nfunction replacePluginRegistration(\n config: OpenCodeGlobalConfig,\n pluginSpec: string,\n): OpenCodeGlobalConfig {\n const currentPlugins = Array.isArray(config.plugin)\n ? config.plugin.filter((item): item is string => typeof item === \"string\")\n : [];\n const nextPlugins: string[] = [];\n let inserted = false;\n\n for (const currentPlugin of currentPlugins) {\n if (isOpencodeTbotPluginSpec(currentPlugin)) {\n if (!inserted) {\n nextPlugins.push(pluginSpec);\n inserted = true;\n }\n\n continue;\n }\n\n nextPlugins.push(currentPlugin);\n }\n\n if (!inserted) {\n nextPlugins.push(pluginSpec);\n }\n\n return {\n ...config,\n plugin: nextPlugins,\n };\n}\n\nasync function readPluginConfigFile(configFilePath: string): Promise<PluginConfigSource> {\n try {\n const content = await readFile(configFilePath, \"utf8\");\n const parsed = JSON.parse(content) as unknown;\n\n return isPlainObject(parsed)\n ? parsed as PluginConfigSource\n : {};\n } catch (error) {\n if (isMissingFileError(error)) {\n return {};\n }\n\n throw error;\n }\n}\n\nasync function readJsoncObject<TObject extends Record<string, unknown>>(\n filePath: string,\n): Promise<TObject> {\n try {\n const content = await readFile(filePath, \"utf8\");\n const parsed = parse(content) as unknown;\n\n return isPlainObject(parsed)\n ? parsed as TObject\n : {} as TObject;\n } catch (error) {\n if (isMissingFileError(error)) {\n return {} as TObject;\n }\n\n throw error;\n }\n}\n\nasync function writeJsonFile(filePath: string, value: Record<string, unknown>): Promise<void> {\n await ensureParentDirectory(filePath);\n await writeFile(filePath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nasync function ensureParentDirectory(filePath: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n}\n\nexport function createPromptSession(options: CreatePromptSessionOptions = {}): PromptSessionLike {\n const input = options.input ?? stdin;\n const output = options.output ?? stdout;\n\n if (!input.isTTY || !output.isTTY) {\n return {\n ask: async () => \"\",\n askSecret: async () => \"\",\n async confirm(_question: string, defaultValue: boolean) {\n return defaultValue;\n },\n close() { },\n };\n }\n\n return {\n ask(question: string) {\n return askQuestion(input, output, question);\n },\n askSecret(question: string) {\n return askSecretQuestion(input, output, question);\n },\n async confirm(question: string, defaultValue: boolean) {\n const answer = normalizeOptionalString(await askQuestion(input, output, question));\n\n if (!answer) {\n return defaultValue;\n }\n\n if ([\"y\", \"yes\"].includes(answer.toLowerCase())) {\n return true;\n }\n\n if ([\"n\", \"no\"].includes(answer.toLowerCase())) {\n return false;\n }\n\n throw new Error(`Unsupported answer: ${answer}`);\n },\n close() { },\n };\n}\n\nasync function askQuestion(\n input: NodeJS.ReadStream,\n output: NodeJS.WriteStream,\n question: string,\n): Promise<string> {\n const readline = createInterface({ input, output });\n\n try {\n return await readline.question(question);\n } finally {\n readline.close();\n }\n}\n\nasync function askSecretQuestion(\n input: NodeJS.ReadStream,\n output: NodeJS.WriteStream,\n question: string,\n): Promise<string> {\n if (typeof input.setRawMode !== \"function\") {\n return askQuestion(input, output, question);\n }\n\n output.write(question);\n\n try {\n return await readMaskedInput(input, output);\n } finally {\n output.write(\"\\n\");\n }\n}\n\nasync function readMaskedInput(\n input: NodeJS.ReadStream,\n output: NodeJS.WriteStream,\n): Promise<string> {\n return new Promise((resolvePromise, rejectPromise) => {\n const buffer: string[] = [];\n let settled = false;\n\n const cleanup = () => {\n input.off(\"data\", handleData);\n input.off(\"error\", handleError);\n input.pause();\n input.setRawMode?.(false);\n };\n\n const rejectWith = (error: unknown) => {\n if (settled) {\n return;\n }\n\n settled = true;\n cleanup();\n rejectPromise(error);\n };\n\n const resolveWith = (value: string) => {\n if (settled) {\n return;\n }\n\n settled = true;\n cleanup();\n resolvePromise(value);\n };\n\n const handleError = (error: unknown) => {\n rejectWith(error);\n };\n\n const handleData = (chunk: string | Buffer) => {\n const text = Buffer.isBuffer(chunk) ? chunk.toString(\"utf8\") : chunk;\n\n for (const character of text) {\n if (character === \"\\r\" || character === \"\\n\") {\n resolveWith(buffer.join(\"\"));\n return;\n }\n\n if (character === \"\\u0003\") {\n rejectWith(new Error(PROMPT_CANCELLED_ERROR));\n return;\n }\n\n if (character === \"\\u0008\" || character === \"\\u007f\") {\n if (buffer.length > 0) {\n buffer.pop();\n output.write(\"\\b \\b\");\n }\n continue;\n }\n\n if (character < \" \" || character === \"\\u007f\") {\n continue;\n }\n\n buffer.push(character);\n output.write(\"*\");\n }\n };\n\n input.setRawMode?.(true);\n input.resume();\n input.on(\"error\", handleError);\n input.on(\"data\", handleData);\n });\n}\n\nfunction normalizeOptionalString(value: string | undefined | null): string | null {\n const normalized = value?.trim();\n\n return normalized\n ? normalized\n : null;\n}\n\nfunction normalizeRequiredString(value: string | undefined | null, errorMessage: string): string {\n const normalized = normalizeOptionalString(value);\n\n if (!normalized) {\n throw new Error(errorMessage);\n }\n\n return normalized;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isMissingFileError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\nfunction isOpencodeTbotPluginSpec(value: string): boolean {\n const normalized = value.trim();\n\n return normalized === \"opencode-tbot\" || normalized.startsWith(\"opencode-tbot@\");\n}\n\nasync function warnAboutLegacyLocalInstallations(homeDir: string): Promise<void> {\n const legacyInstallations = await findLegacyLocalInstallations(homeDir);\n\n if (legacyInstallations.length === 0) {\n return;\n }\n\n stdout.write(\"\\nDetected local opencode-tbot npm installation(s) that can make OpenCode show the plugin as file:///.../node_modules/...\\n\");\n\n for (const installation of legacyInstallations) {\n stdout.write(`- ${installation.packagePath}\\n`);\n stdout.write(` cleanup: cd \"${installation.rootDir}\" && npm uninstall opencode-tbot\\n`);\n }\n\n stdout.write(\"Recommended npm flow: npm exec --package opencode-tbot@latest opencode-tbot -- install\\n\");\n}\n\nasync function findLegacyLocalInstallations(homeDir: string) {\n const roots = [...new Set([\n resolve(process.cwd()),\n resolve(homeDir),\n ])];\n const installations: Array<{ packagePath: string; rootDir: string }> = [];\n\n for (const rootDir of roots) {\n const packagePath = join(rootDir, \"node_modules\", \"opencode-tbot\", \"package.json\");\n\n if (await pathExists(packagePath)) {\n installations.push({\n packagePath: join(rootDir, \"node_modules\", \"opencode-tbot\"),\n rootDir,\n });\n }\n }\n\n return installations;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction buildHelpText(): string {\n return [\n \"Usage: opencode-tbot [install|update] [options]\",\n \" opencode-tbot --version\",\n \"\",\n \"Recommended npm usage:\",\n \" npm exec --package opencode-tbot@latest opencode-tbot -- install\",\n \" npm exec --package opencode-tbot@latest opencode-tbot -- update\",\n \" npm exec --package opencode-tbot@latest opencode-tbot -- --version\",\n \"\",\n \"Commands:\",\n \" install\",\n \" update\",\n \"\",\n \"Options:\",\n \" --bot-token <token>\",\n \" --enable-voice\",\n \" --disable-voice\",\n \" --openrouter-api-key <key>\",\n \" --telegram-api-root <url>\",\n \" --plugin-spec <spec>\",\n \" --skip-register\",\n \" --home-dir <path>\",\n \" --version\",\n \" --help\",\n ].join(\"\\n\");\n}\n\nfunction formatCliError(error: unknown): string {\n return error instanceof Error && error.message.trim().length > 0\n ? error.message.trim()\n : String(error);\n}\n"],"mappings":"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"}
|
package/dist/plugin.js
CHANGED
|
@@ -355,9 +355,11 @@ var OpenCodeClient = class {
|
|
|
355
355
|
async resolvePromptResponse(input, data, knownMessageIds) {
|
|
356
356
|
if (!shouldPollPromptMessage(data, input.structured ?? false)) return data;
|
|
357
357
|
const messageId = data.info?.id;
|
|
358
|
+
let expectedParentId = toAssistantMessage(data.info)?.parentID ?? null;
|
|
358
359
|
let bestCandidate = data;
|
|
359
360
|
if (!messageId) return await this.findLatestPromptResponse(input.sessionId, {
|
|
360
361
|
initialMessageId: null,
|
|
362
|
+
initialParentId: expectedParentId,
|
|
361
363
|
knownMessageIds,
|
|
362
364
|
structured: input.structured ?? false
|
|
363
365
|
}) ?? data;
|
|
@@ -367,6 +369,7 @@ var OpenCodeClient = class {
|
|
|
367
369
|
if (!next) {
|
|
368
370
|
const latest = await this.findLatestPromptResponse(input.sessionId, {
|
|
369
371
|
initialMessageId: messageId,
|
|
372
|
+
initialParentId: expectedParentId,
|
|
370
373
|
knownMessageIds,
|
|
371
374
|
structured: input.structured ?? false
|
|
372
375
|
});
|
|
@@ -378,9 +381,11 @@ var OpenCodeClient = class {
|
|
|
378
381
|
}
|
|
379
382
|
data = next;
|
|
380
383
|
bestCandidate = next;
|
|
384
|
+
expectedParentId = toAssistantMessage(next.info)?.parentID ?? expectedParentId;
|
|
381
385
|
if (!shouldPollPromptMessage(data, input.structured ?? false)) return data;
|
|
382
386
|
const latest = await this.findLatestPromptResponse(input.sessionId, {
|
|
383
387
|
initialMessageId: messageId,
|
|
388
|
+
initialParentId: expectedParentId,
|
|
384
389
|
knownMessageIds,
|
|
385
390
|
structured: input.structured ?? false
|
|
386
391
|
});
|
|
@@ -431,10 +436,11 @@ var OpenCodeClient = class {
|
|
|
431
436
|
isInitial: !!id && id === options.initialMessageId,
|
|
432
437
|
isNew: !!id && !options.knownMessageIds.has(id),
|
|
433
438
|
isUsable: !shouldPollPromptMessage(message, options.structured),
|
|
439
|
+
sharesParent: !!assistant?.parentID && assistant.parentID === options.initialParentId,
|
|
434
440
|
message
|
|
435
441
|
};
|
|
436
|
-
}).sort((left, right) => Number(right.isUsable) - Number(left.isUsable) || Number(right.
|
|
437
|
-
return (candidates.find((candidate) => candidate.isUsable && (candidate.isNew
|
|
442
|
+
}).sort((left, right) => Number(right.isUsable) - Number(left.isUsable) || Number(right.isInitial) - Number(left.isInitial) || Number(right.sharesParent) - Number(left.sharesParent) || Number(right.isNew) - Number(left.isNew) || right.createdAt - left.createdAt);
|
|
443
|
+
return (candidates.find((candidate) => candidate.isUsable && candidate.isInitial) ?? candidates.find((candidate) => candidate.isUsable && candidate.sharesParent && candidate.isNew) ?? candidates.find((candidate) => candidate.isUsable && candidate.isNew) ?? candidates.find((candidate) => candidate.isInitial) ?? candidates.find((candidate) => candidate.sharesParent && candidate.isNew) ?? candidates.find((candidate) => candidate.isNew) ?? null)?.message ?? null;
|
|
438
444
|
}
|
|
439
445
|
async loadModels() {
|
|
440
446
|
const [configResponse, providersResponse] = await Promise.all([this.client.config.get(void 0, SDK_OPTIONS), this.client.config.providers(void 0, SDK_OPTIONS)]);
|
|
@@ -557,8 +563,7 @@ function shouldPollPromptMessage(data, structured) {
|
|
|
557
563
|
const bodyMd = structured ? extractStructuredMarkdown(assistantInfo?.structured) : null;
|
|
558
564
|
const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
|
|
559
565
|
const hasAssistantError = !!assistantInfo?.error;
|
|
560
|
-
|
|
561
|
-
return !hasText && !bodyMd && !hasAssistantError && !isCompleted;
|
|
566
|
+
return !hasText && !bodyMd && !hasAssistantError;
|
|
562
567
|
}
|
|
563
568
|
function toAssistantMessage(message) {
|
|
564
569
|
if (!message || typeof message !== "object") return null;
|