opencode-tbot 0.1.5 → 0.1.7
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/plugin.js +133 -14
- 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
|
-
|
|
25
|
+
npm install opencode-tbot@latest
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
The installer registers the plugin globally and writes the default runtime config.
|
package/README.zh-CN.md
CHANGED
package/dist/plugin.js
CHANGED
|
@@ -214,6 +214,17 @@ function buildOpenCodeSdkConfig(options) {
|
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
var EMPTY_RESPONSE_TEXT = "OpenCode returned empty response.";
|
|
217
|
+
var PROMPT_MESSAGE_POLL_DELAYS_MS = [
|
|
218
|
+
0,
|
|
219
|
+
100,
|
|
220
|
+
250,
|
|
221
|
+
500,
|
|
222
|
+
1e3,
|
|
223
|
+
2e3,
|
|
224
|
+
4e3,
|
|
225
|
+
8e3
|
|
226
|
+
];
|
|
227
|
+
var PROMPT_MESSAGE_POLL_LIMIT = 20;
|
|
217
228
|
var STRUCTURED_REPLY_SCHEMA = {
|
|
218
229
|
type: "json_schema",
|
|
219
230
|
retryCount: 2,
|
|
@@ -327,7 +338,8 @@ var OpenCodeClient = class {
|
|
|
327
338
|
url: file.url
|
|
328
339
|
}))];
|
|
329
340
|
if (parts.length === 0) throw new Error("Prompt requires text or file attachments.");
|
|
330
|
-
const
|
|
341
|
+
const knownMessageIds = await this.captureKnownMessageIds(input.sessionId);
|
|
342
|
+
const initialData = unwrapSdkData(await this.client.session.prompt({
|
|
331
343
|
sessionID: input.sessionId,
|
|
332
344
|
...input.agent ? { agent: input.agent } : {},
|
|
333
345
|
...input.structured ? { format: STRUCTURED_REPLY_SCHEMA } : {},
|
|
@@ -335,19 +347,96 @@ var OpenCodeClient = class {
|
|
|
335
347
|
...input.variant ? { variant: input.variant } : {},
|
|
336
348
|
parts
|
|
337
349
|
}, SDK_OPTIONS));
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
350
|
+
return buildPromptSessionResult(await this.resolvePromptResponse(input, initialData, knownMessageIds), {
|
|
351
|
+
emptyResponseText: EMPTY_RESPONSE_TEXT,
|
|
352
|
+
finishedAt: Date.now(),
|
|
353
|
+
startedAt,
|
|
354
|
+
structured: input.structured ?? false
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
async resolvePromptResponse(input, data, knownMessageIds) {
|
|
358
|
+
if (!shouldPollPromptMessage(data, input.structured ?? false)) return data;
|
|
359
|
+
const messageId = data.info?.id;
|
|
360
|
+
let bestCandidate = data;
|
|
361
|
+
if (!messageId) return await this.findLatestPromptResponse(input.sessionId, {
|
|
362
|
+
initialMessageId: null,
|
|
363
|
+
knownMessageIds,
|
|
364
|
+
structured: input.structured ?? false
|
|
365
|
+
}) ?? data;
|
|
366
|
+
for (const delayMs of PROMPT_MESSAGE_POLL_DELAYS_MS) {
|
|
367
|
+
if (delayMs > 0) await delay(delayMs);
|
|
368
|
+
const next = await this.fetchPromptMessage(input.sessionId, messageId);
|
|
369
|
+
if (!next) {
|
|
370
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, {
|
|
371
|
+
initialMessageId: messageId,
|
|
372
|
+
knownMessageIds,
|
|
373
|
+
structured: input.structured ?? false
|
|
374
|
+
});
|
|
375
|
+
if (latest) {
|
|
376
|
+
bestCandidate = latest;
|
|
377
|
+
if (!shouldPollPromptMessage(bestCandidate, input.structured ?? false)) return bestCandidate;
|
|
378
|
+
}
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
data = next;
|
|
382
|
+
bestCandidate = next;
|
|
383
|
+
if (!shouldPollPromptMessage(data, input.structured ?? false)) return data;
|
|
384
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, {
|
|
385
|
+
initialMessageId: messageId,
|
|
386
|
+
knownMessageIds,
|
|
387
|
+
structured: input.structured ?? false
|
|
388
|
+
});
|
|
389
|
+
if (latest) {
|
|
390
|
+
bestCandidate = latest;
|
|
391
|
+
if (!shouldPollPromptMessage(bestCandidate, input.structured ?? false)) return bestCandidate;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return bestCandidate;
|
|
395
|
+
}
|
|
396
|
+
async fetchPromptMessage(sessionId, messageId) {
|
|
397
|
+
if (typeof this.client.session.message !== "function") return null;
|
|
398
|
+
try {
|
|
399
|
+
return unwrapSdkData(await this.client.session.message({
|
|
400
|
+
sessionID: sessionId,
|
|
401
|
+
messageID: messageId
|
|
402
|
+
}, SDK_OPTIONS));
|
|
403
|
+
} catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async captureKnownMessageIds(sessionId) {
|
|
408
|
+
const messages = await this.fetchRecentPromptMessages(sessionId);
|
|
409
|
+
if (!messages) return /* @__PURE__ */ new Set();
|
|
410
|
+
return new Set(messages.map((message) => message.info?.id).filter((id) => typeof id === "string" && id.length > 0));
|
|
411
|
+
}
|
|
412
|
+
async fetchRecentPromptMessages(sessionId) {
|
|
413
|
+
if (typeof this.client.session.messages !== "function") return null;
|
|
414
|
+
try {
|
|
415
|
+
const data = unwrapSdkData(await this.client.session.messages({
|
|
416
|
+
sessionID: sessionId,
|
|
417
|
+
limit: PROMPT_MESSAGE_POLL_LIMIT
|
|
418
|
+
}, SDK_OPTIONS));
|
|
419
|
+
return Array.isArray(data) ? data : null;
|
|
420
|
+
} catch {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async findLatestPromptResponse(sessionId, options) {
|
|
425
|
+
const messages = await this.fetchRecentPromptMessages(sessionId);
|
|
426
|
+
if (!messages || messages.length === 0) return null;
|
|
427
|
+
const candidates = messages.filter((message) => toAssistantMessage(message.info) !== null).map((message) => {
|
|
428
|
+
const assistant = toAssistantMessage(message.info);
|
|
429
|
+
const id = assistant?.id ?? null;
|
|
430
|
+
return {
|
|
431
|
+
createdAt: typeof assistant?.time?.created === "number" && Number.isFinite(assistant.time.created) ? assistant.time.created : 0,
|
|
432
|
+
id,
|
|
433
|
+
isInitial: !!id && id === options.initialMessageId,
|
|
434
|
+
isNew: !!id && !options.knownMessageIds.has(id),
|
|
435
|
+
isUsable: !shouldPollPromptMessage(message, options.structured),
|
|
436
|
+
message
|
|
437
|
+
};
|
|
438
|
+
}).sort((left, right) => Number(right.isUsable) - Number(left.isUsable) || Number(right.isNew) - Number(left.isNew) || Number(right.isInitial) - Number(left.isInitial) || right.createdAt - left.createdAt);
|
|
439
|
+
return (candidates.find((candidate) => candidate.isUsable && (candidate.isNew || candidate.isInitial)) ?? candidates.find((candidate) => candidate.isNew || candidate.isInitial) ?? null)?.message ?? null;
|
|
351
440
|
}
|
|
352
441
|
async loadModels() {
|
|
353
442
|
const [configResponse, providersResponse] = await Promise.all([this.client.config.get(void 0, SDK_OPTIONS), this.client.config.providers(void 0, SDK_OPTIONS)]);
|
|
@@ -450,6 +539,36 @@ function extractTextFromParts(parts) {
|
|
|
450
539
|
if (!Array.isArray(parts)) return "";
|
|
451
540
|
return parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
|
|
452
541
|
}
|
|
542
|
+
function buildPromptSessionResult(data, options) {
|
|
543
|
+
const assistantInfo = toAssistantMessage(data.info);
|
|
544
|
+
const bodyMd = options.structured ? extractStructuredMarkdown(assistantInfo?.structured) : null;
|
|
545
|
+
const responseParts = Array.isArray(data.parts) ? data.parts : [];
|
|
546
|
+
const fallbackText = extractTextFromParts(responseParts) || bodyMd || options.emptyResponseText;
|
|
547
|
+
return {
|
|
548
|
+
assistantError: assistantInfo?.error ?? null,
|
|
549
|
+
bodyMd,
|
|
550
|
+
fallbackText,
|
|
551
|
+
info: assistantInfo,
|
|
552
|
+
metrics: extractPromptMetrics(assistantInfo, options.startedAt, options.finishedAt),
|
|
553
|
+
parts: responseParts,
|
|
554
|
+
structured: assistantInfo?.structured ?? null
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function shouldPollPromptMessage(data, structured) {
|
|
558
|
+
const assistantInfo = toAssistantMessage(data.info);
|
|
559
|
+
const bodyMd = structured ? extractStructuredMarkdown(assistantInfo?.structured) : null;
|
|
560
|
+
const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
|
|
561
|
+
const hasAssistantError = !!assistantInfo?.error;
|
|
562
|
+
const isCompleted = typeof assistantInfo?.time?.completed === "number" && Number.isFinite(assistantInfo.time.completed);
|
|
563
|
+
return !hasText && !bodyMd && !hasAssistantError && !isCompleted;
|
|
564
|
+
}
|
|
565
|
+
function toAssistantMessage(message) {
|
|
566
|
+
if (!message || typeof message !== "object") return null;
|
|
567
|
+
return !("role" in message) || message.role === "assistant" ? message : null;
|
|
568
|
+
}
|
|
569
|
+
function delay(ms) {
|
|
570
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
571
|
+
}
|
|
453
572
|
function extractStructuredMarkdown(structured) {
|
|
454
573
|
const parsed = StructuredReplySchema.safeParse(structured);
|
|
455
574
|
if (!parsed.success) return null;
|