@sympoies/dsh-telegram 0.0.0-bootstrap.0 → 0.6.0
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/LICENSE +1 -2
- package/NOTICE +19 -0
- package/README.md +589 -2
- package/cordis.patch.yml +14 -0
- package/lib/access.d.ts +76 -0
- package/lib/access.d.ts.map +1 -0
- package/lib/access.js +137 -0
- package/lib/access.js.map +1 -0
- package/lib/client.js +835 -0
- package/lib/commands.d.ts +51 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +83 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +125 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +97 -0
- package/lib/config.js.map +1 -0
- package/lib/diagnostics.d.ts +58 -0
- package/lib/diagnostics.d.ts.map +1 -0
- package/lib/diagnostics.js +59 -0
- package/lib/diagnostics.js.map +1 -0
- package/lib/failures.d.ts +71 -0
- package/lib/failures.d.ts.map +1 -0
- package/lib/failures.js +139 -0
- package/lib/failures.js.map +1 -0
- package/lib/harness/host.d.ts +106 -0
- package/lib/harness/host.d.ts.map +1 -0
- package/lib/harness/host.js +142 -0
- package/lib/harness/host.js.map +1 -0
- package/lib/harness/message.d.ts +45 -0
- package/lib/harness/message.d.ts.map +1 -0
- package/lib/harness/message.js +28 -0
- package/lib/harness/message.js.map +1 -0
- package/lib/harness/model-selection.d.ts +82 -0
- package/lib/harness/model-selection.d.ts.map +1 -0
- package/lib/harness/model-selection.js +103 -0
- package/lib/harness/model-selection.js.map +1 -0
- package/lib/harness/questions-seam.d.ts +34 -0
- package/lib/harness/questions-seam.d.ts.map +1 -0
- package/lib/harness/questions-seam.js +45 -0
- package/lib/harness/questions-seam.js.map +1 -0
- package/lib/harness/types.d.ts +108 -0
- package/lib/harness/types.d.ts.map +1 -0
- package/lib/harness/types.js +23 -0
- package/lib/harness/types.js.map +1 -0
- package/lib/index.d.ts +65 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +977 -0
- package/lib/index.js.map +1 -0
- package/lib/interact/approvals.d.ts +59 -0
- package/lib/interact/approvals.d.ts.map +1 -0
- package/lib/interact/approvals.js +132 -0
- package/lib/interact/approvals.js.map +1 -0
- package/lib/interact/pending.d.ts +56 -0
- package/lib/interact/pending.d.ts.map +1 -0
- package/lib/interact/pending.js +89 -0
- package/lib/interact/pending.js.map +1 -0
- package/lib/interact/questions.d.ts +100 -0
- package/lib/interact/questions.d.ts.map +1 -0
- package/lib/interact/questions.js +255 -0
- package/lib/interact/questions.js.map +1 -0
- package/lib/interact/surface.d.ts +33 -0
- package/lib/interact/surface.d.ts.map +1 -0
- package/lib/interact/surface.js +39 -0
- package/lib/interact/surface.js.map +1 -0
- package/lib/interact/text-capture.d.ts +38 -0
- package/lib/interact/text-capture.d.ts.map +1 -0
- package/lib/interact/text-capture.js +75 -0
- package/lib/interact/text-capture.js.map +1 -0
- package/lib/media/collect.d.ts +157 -0
- package/lib/media/collect.d.ts.map +1 -0
- package/lib/media/collect.js +235 -0
- package/lib/media/collect.js.map +1 -0
- package/lib/media/extractor.d.ts +117 -0
- package/lib/media/extractor.d.ts.map +1 -0
- package/lib/media/extractor.js +207 -0
- package/lib/media/extractor.js.map +1 -0
- package/lib/media/intake.d.ts +57 -0
- package/lib/media/intake.d.ts.map +1 -0
- package/lib/media/intake.js +115 -0
- package/lib/media/intake.js.map +1 -0
- package/lib/media/limits.d.ts +69 -0
- package/lib/media/limits.d.ts.map +1 -0
- package/lib/media/limits.js +97 -0
- package/lib/media/limits.js.map +1 -0
- package/lib/media/ocr.d.ts +66 -0
- package/lib/media/ocr.d.ts.map +1 -0
- package/lib/media/ocr.js +131 -0
- package/lib/media/ocr.js.map +1 -0
- package/lib/media/screenshot.d.ts +57 -0
- package/lib/media/screenshot.d.ts.map +1 -0
- package/lib/media/screenshot.js +101 -0
- package/lib/media/screenshot.js.map +1 -0
- package/lib/media/vision.d.ts +60 -0
- package/lib/media/vision.d.ts.map +1 -0
- package/lib/media/vision.js +74 -0
- package/lib/media/vision.js.map +1 -0
- package/lib/render/clamp.d.ts +34 -0
- package/lib/render/clamp.d.ts.map +1 -0
- package/lib/render/clamp.js +65 -0
- package/lib/render/clamp.js.map +1 -0
- package/lib/render/escape.d.ts +15 -0
- package/lib/render/escape.d.ts.map +1 -0
- package/lib/render/escape.js +17 -0
- package/lib/render/escape.js.map +1 -0
- package/lib/reply/activity.d.ts +31 -0
- package/lib/reply/activity.d.ts.map +1 -0
- package/lib/reply/activity.js +88 -0
- package/lib/reply/activity.js.map +1 -0
- package/lib/reply/rich-stream.d.ts +173 -0
- package/lib/reply/rich-stream.d.ts.map +1 -0
- package/lib/reply/rich-stream.js +286 -0
- package/lib/reply/rich-stream.js.map +1 -0
- package/lib/reply/split-markdown.d.ts +21 -0
- package/lib/reply/split-markdown.d.ts.map +1 -0
- package/lib/reply/split-markdown.js +87 -0
- package/lib/reply/split-markdown.js.map +1 -0
- package/lib/reply/turn-bridge.d.ts +143 -0
- package/lib/reply/turn-bridge.d.ts.map +1 -0
- package/lib/reply/turn-bridge.js +182 -0
- package/lib/reply/turn-bridge.js.map +1 -0
- package/lib/router.d.ts +415 -0
- package/lib/router.d.ts.map +1 -0
- package/lib/router.js +663 -0
- package/lib/router.js.map +1 -0
- package/lib/secrets.d.ts +30 -0
- package/lib/secrets.d.ts.map +1 -0
- package/lib/secrets.js +51 -0
- package/lib/secrets.js.map +1 -0
- package/lib/session/bindings.d.ts +81 -0
- package/lib/session/bindings.d.ts.map +1 -0
- package/lib/session/bindings.js +170 -0
- package/lib/session/bindings.js.map +1 -0
- package/lib/session/history.d.ts +50 -0
- package/lib/session/history.d.ts.map +1 -0
- package/lib/session/history.js +127 -0
- package/lib/session/history.js.map +1 -0
- package/lib/session/models.d.ts +99 -0
- package/lib/session/models.d.ts.map +1 -0
- package/lib/session/models.js +123 -0
- package/lib/session/models.js.map +1 -0
- package/lib/session/permission.d.ts +83 -0
- package/lib/session/permission.d.ts.map +1 -0
- package/lib/session/permission.js +107 -0
- package/lib/session/permission.js.map +1 -0
- package/lib/session/picker.d.ts +49 -0
- package/lib/session/picker.d.ts.map +1 -0
- package/lib/session/picker.js +107 -0
- package/lib/session/picker.js.map +1 -0
- package/lib/session/preferences.d.ts +55 -0
- package/lib/session/preferences.d.ts.map +1 -0
- package/lib/session/preferences.js +102 -0
- package/lib/session/preferences.js.map +1 -0
- package/lib/session/recovery.d.ts +66 -0
- package/lib/session/recovery.d.ts.map +1 -0
- package/lib/session/recovery.js +112 -0
- package/lib/session/recovery.js.map +1 -0
- package/lib/session/runner.d.ts +223 -0
- package/lib/session/runner.d.ts.map +1 -0
- package/lib/session/runner.js +260 -0
- package/lib/session/runner.js.map +1 -0
- package/lib/session/workspaces.d.ts +28 -0
- package/lib/session/workspaces.d.ts.map +1 -0
- package/lib/session/workspaces.js +53 -0
- package/lib/session/workspaces.js.map +1 -0
- package/lib/telegram/addressing.d.ts +39 -0
- package/lib/telegram/addressing.d.ts.map +1 -0
- package/lib/telegram/addressing.js +81 -0
- package/lib/telegram/addressing.js.map +1 -0
- package/lib/telegram/albums.d.ts +46 -0
- package/lib/telegram/albums.d.ts.map +1 -0
- package/lib/telegram/albums.js +104 -0
- package/lib/telegram/albums.js.map +1 -0
- package/lib/telegram/api.d.ts +238 -0
- package/lib/telegram/api.d.ts.map +1 -0
- package/lib/telegram/api.js +518 -0
- package/lib/telegram/api.js.map +1 -0
- package/lib/telegram/poller.d.ts +59 -0
- package/lib/telegram/poller.d.ts.map +1 -0
- package/lib/telegram/poller.js +94 -0
- package/lib/telegram/poller.js.map +1 -0
- package/lib/telegram/types.d.ts +130 -0
- package/lib/telegram/types.d.ts.map +1 -0
- package/lib/telegram/types.js +9 -0
- package/lib/telegram/types.js.map +1 -0
- package/lib/telegram/typing.d.ts +47 -0
- package/lib/telegram/typing.d.ts.map +1 -0
- package/lib/telegram/typing.js +91 -0
- package/lib/telegram/typing.js.map +1 -0
- package/lib/versions.d.ts +63 -0
- package/lib/versions.d.ts.map +1 -0
- package/lib/versions.js +135 -0
- package/lib/versions.js.map +1 -0
- package/package.json +94 -10
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning the harness's session-event feed into a live Telegram reply.
|
|
3
|
+
*
|
|
4
|
+
* The harness publishes a durable log — `turn/start`, a run of
|
|
5
|
+
* `assistant/chunk` deltas, `assistant/message`, `turn/end` — and every
|
|
6
|
+
* consumer reads the same feed. This bridge subscribes for Telegram-bound
|
|
7
|
+
* sessions only and drives one {@link ReplyStream} per open turn.
|
|
8
|
+
*
|
|
9
|
+
* Only visible model text is forwarded. Reasoning deltas and tool-call deltas
|
|
10
|
+
* stay out of the chat: the first is the model thinking aloud, the second is
|
|
11
|
+
* an argument fragment, and neither is an answer to the person waiting.
|
|
12
|
+
*
|
|
13
|
+
* `assistant/message` is treated as authoritative over the accumulated deltas.
|
|
14
|
+
* A turn cancelled mid-stream still emits it with `interrupted: true`, and it
|
|
15
|
+
* is the only place the finished text is guaranteed complete.
|
|
16
|
+
*/
|
|
17
|
+
import type { ChatTarget } from '../interact/surface.js';
|
|
18
|
+
import type { Logger } from '../harness/types.js';
|
|
19
|
+
import type { RichChat } from './rich-stream.js';
|
|
20
|
+
/** The session-event shapes this bridge reads. */
|
|
21
|
+
export type SessionEvent = {
|
|
22
|
+
readonly type: 'turn/start';
|
|
23
|
+
readonly data: {
|
|
24
|
+
readonly turn: number;
|
|
25
|
+
};
|
|
26
|
+
} | {
|
|
27
|
+
readonly type: 'assistant/chunk';
|
|
28
|
+
readonly data: {
|
|
29
|
+
readonly turn: number;
|
|
30
|
+
readonly chunk: {
|
|
31
|
+
readonly type: string;
|
|
32
|
+
readonly text?: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
} | {
|
|
36
|
+
readonly type: 'assistant/message';
|
|
37
|
+
readonly data: {
|
|
38
|
+
readonly turn: number;
|
|
39
|
+
readonly message: {
|
|
40
|
+
readonly content?: readonly {
|
|
41
|
+
type: string;
|
|
42
|
+
text?: string;
|
|
43
|
+
}[];
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
} | {
|
|
47
|
+
readonly type: 'tool/call';
|
|
48
|
+
readonly data: {
|
|
49
|
+
readonly turn: number;
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly arguments?: string;
|
|
52
|
+
};
|
|
53
|
+
} | {
|
|
54
|
+
readonly type: 'tool/result';
|
|
55
|
+
readonly data: {
|
|
56
|
+
readonly turn: number;
|
|
57
|
+
};
|
|
58
|
+
} | {
|
|
59
|
+
readonly type: 'turn/end';
|
|
60
|
+
readonly data: {
|
|
61
|
+
readonly turn: number;
|
|
62
|
+
readonly reason?: {
|
|
63
|
+
readonly kind: string;
|
|
64
|
+
readonly error?: {
|
|
65
|
+
message: string;
|
|
66
|
+
code?: string;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
} | {
|
|
71
|
+
readonly type: string;
|
|
72
|
+
readonly data?: unknown;
|
|
73
|
+
};
|
|
74
|
+
/** Construction options. */
|
|
75
|
+
export interface TurnBridgeOptions {
|
|
76
|
+
readonly chat: RichChat;
|
|
77
|
+
/** Resolve a session id to its chat, or undefined when it is not a Telegram one. */
|
|
78
|
+
readonly targetOf: (sessionId: string) => ChatTarget | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Whether a conversation can stream through drafts. Only private chats can:
|
|
81
|
+
* `sendRichMessageDraft` takes a private chat id and nothing else.
|
|
82
|
+
*/
|
|
83
|
+
readonly canDraft: (target: ChatTarget) => boolean;
|
|
84
|
+
readonly throttleMs?: number;
|
|
85
|
+
readonly heartbeatMs?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Called when a turn ends in failure.
|
|
88
|
+
*
|
|
89
|
+
* A failed turn produced no reply, so without this the conversation simply
|
|
90
|
+
* goes quiet — which reads as a broken bot rather than as a refused request.
|
|
91
|
+
*/
|
|
92
|
+
readonly onFailure?: (sessionId: string, failure: {
|
|
93
|
+
message: string;
|
|
94
|
+
code?: string;
|
|
95
|
+
}) => void;
|
|
96
|
+
/**
|
|
97
|
+
* Keeps Telegram's own indicator alive from the moment a turn opens until it
|
|
98
|
+
* has something to show. Absent leaves the chat quiet until the reply lands.
|
|
99
|
+
*/
|
|
100
|
+
readonly typing?: {
|
|
101
|
+
hold(target: ChatTarget): () => void;
|
|
102
|
+
};
|
|
103
|
+
readonly logger?: Logger;
|
|
104
|
+
}
|
|
105
|
+
export declare class TurnBridge {
|
|
106
|
+
private readonly options;
|
|
107
|
+
private readonly active;
|
|
108
|
+
private readonly logger;
|
|
109
|
+
constructor(options: TurnBridgeOptions);
|
|
110
|
+
/**
|
|
111
|
+
* Consume one session event.
|
|
112
|
+
*
|
|
113
|
+
* @param sessionId - the session the event belongs to.
|
|
114
|
+
* @param event - the event from the harness feed.
|
|
115
|
+
*/
|
|
116
|
+
handle(sessionId: string, event: SessionEvent): Promise<void>;
|
|
117
|
+
/** Whether a turn is currently streaming for a session. */
|
|
118
|
+
isStreaming(sessionId: string): boolean;
|
|
119
|
+
/** Close every open stream — the plugin is unloading mid-turn. */
|
|
120
|
+
dispose(): Promise<void>;
|
|
121
|
+
/** Open a reply for a turn in a Telegram-bound session. */
|
|
122
|
+
private onTurnStart;
|
|
123
|
+
/** Forward one visible text delta. */
|
|
124
|
+
private onChunk;
|
|
125
|
+
/**
|
|
126
|
+
* Say which tool is running.
|
|
127
|
+
*
|
|
128
|
+
* These events were previously dropped along with the reasoning deltas, on
|
|
129
|
+
* the grounds that neither is an answer. That is true of the content and
|
|
130
|
+
* wrong about the need: during a long tool call it is the only sign the
|
|
131
|
+
* agent is alive.
|
|
132
|
+
*/
|
|
133
|
+
private onToolCall;
|
|
134
|
+
/** Clear the activity line once the tool has answered. */
|
|
135
|
+
private onToolResult;
|
|
136
|
+
/** Record the authoritative text for the turn. */
|
|
137
|
+
private onMessage;
|
|
138
|
+
/** Deliver the finished reply, or say why there is none. */
|
|
139
|
+
private onTurnEnd;
|
|
140
|
+
/** Finish a stream left open by a turn that never ended. */
|
|
141
|
+
private closeActive;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=turn-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-bridge.d.ts","sourceRoot":"","sources":["../../src/reply/turn-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning the harness's session-event feed into a live Telegram reply.
|
|
3
|
+
*
|
|
4
|
+
* The harness publishes a durable log — `turn/start`, a run of
|
|
5
|
+
* `assistant/chunk` deltas, `assistant/message`, `turn/end` — and every
|
|
6
|
+
* consumer reads the same feed. This bridge subscribes for Telegram-bound
|
|
7
|
+
* sessions only and drives one {@link ReplyStream} per open turn.
|
|
8
|
+
*
|
|
9
|
+
* Only visible model text is forwarded. Reasoning deltas and tool-call deltas
|
|
10
|
+
* stay out of the chat: the first is the model thinking aloud, the second is
|
|
11
|
+
* an argument fragment, and neither is an answer to the person waiting.
|
|
12
|
+
*
|
|
13
|
+
* `assistant/message` is treated as authoritative over the accumulated deltas.
|
|
14
|
+
* A turn cancelled mid-stream still emits it with `interrupted: true`, and it
|
|
15
|
+
* is the only place the finished text is guaranteed complete.
|
|
16
|
+
*/
|
|
17
|
+
import { SILENT_LOGGER } from '../harness/types.js';
|
|
18
|
+
import { describeToolCall } from './activity.js';
|
|
19
|
+
import { RichReplyStream } from './rich-stream.js';
|
|
20
|
+
export class TurnBridge {
|
|
21
|
+
options;
|
|
22
|
+
active = new Map();
|
|
23
|
+
logger;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.options = options;
|
|
26
|
+
this.logger = options.logger ?? SILENT_LOGGER;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Consume one session event.
|
|
30
|
+
*
|
|
31
|
+
* @param sessionId - the session the event belongs to.
|
|
32
|
+
* @param event - the event from the harness feed.
|
|
33
|
+
*/
|
|
34
|
+
async handle(sessionId, event) {
|
|
35
|
+
try {
|
|
36
|
+
switch (event.type) {
|
|
37
|
+
case 'turn/start':
|
|
38
|
+
return await this.onTurnStart(sessionId, event);
|
|
39
|
+
case 'assistant/chunk':
|
|
40
|
+
return await this.onChunk(sessionId, event);
|
|
41
|
+
case 'assistant/message':
|
|
42
|
+
return this.onMessage(sessionId, event);
|
|
43
|
+
case 'tool/call':
|
|
44
|
+
return await this.onToolCall(sessionId, event);
|
|
45
|
+
case 'tool/result':
|
|
46
|
+
return await this.onToolResult(sessionId, event);
|
|
47
|
+
case 'turn/end':
|
|
48
|
+
return await this.onTurnEnd(sessionId, event);
|
|
49
|
+
default:
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
this.logger.warn('[dsh-telegram] failed to stream a turn', error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Whether a turn is currently streaming for a session. */
|
|
58
|
+
isStreaming(sessionId) {
|
|
59
|
+
return this.active.has(sessionId);
|
|
60
|
+
}
|
|
61
|
+
/** Close every open stream — the plugin is unloading mid-turn. */
|
|
62
|
+
async dispose() {
|
|
63
|
+
const entries = [...this.active.values()];
|
|
64
|
+
this.active.clear();
|
|
65
|
+
for (const entry of entries)
|
|
66
|
+
entry.release();
|
|
67
|
+
await Promise.all(entries.map((entry) => entry.stream.finish().catch(() => undefined)));
|
|
68
|
+
}
|
|
69
|
+
/** Open a reply for a turn in a Telegram-bound session. */
|
|
70
|
+
async onTurnStart(sessionId, event) {
|
|
71
|
+
const target = this.options.targetOf(sessionId);
|
|
72
|
+
if (!target)
|
|
73
|
+
return;
|
|
74
|
+
// A previous turn that never closed would otherwise leak its stream.
|
|
75
|
+
await this.closeActive(sessionId);
|
|
76
|
+
// Taken before the stream exists, because the gap this covers starts here:
|
|
77
|
+
// a turn can think, or sit in a tool call, long before it writes anything.
|
|
78
|
+
const release = this.options.typing?.hold(target) ?? (() => undefined);
|
|
79
|
+
const stream = new RichReplyStream({
|
|
80
|
+
chat: this.options.chat,
|
|
81
|
+
target,
|
|
82
|
+
canDraft: this.options.canDraft(target),
|
|
83
|
+
// The indicator is a stand-in, so it stops the moment there is something
|
|
84
|
+
// real in its place.
|
|
85
|
+
onVisible: release,
|
|
86
|
+
// Stable for the turn, so Telegram animates one growing preview rather
|
|
87
|
+
// than replacing it. Non-zero is required.
|
|
88
|
+
draftId: draftIdFor(sessionId, event.data.turn),
|
|
89
|
+
...(this.options.throttleMs !== undefined ? { throttleMs: this.options.throttleMs } : {}),
|
|
90
|
+
...(this.options.heartbeatMs !== undefined ? { heartbeatMs: this.options.heartbeatMs } : {}),
|
|
91
|
+
logger: this.logger,
|
|
92
|
+
});
|
|
93
|
+
this.active.set(sessionId, { turn: event.data.turn, stream, release });
|
|
94
|
+
await stream.start();
|
|
95
|
+
}
|
|
96
|
+
/** Forward one visible text delta. */
|
|
97
|
+
async onChunk(sessionId, event) {
|
|
98
|
+
const entry = this.active.get(sessionId);
|
|
99
|
+
if (!entry || entry.turn !== event.data.turn)
|
|
100
|
+
return;
|
|
101
|
+
if (event.data.chunk.type !== 'text-delta')
|
|
102
|
+
return;
|
|
103
|
+
await entry.stream.append(event.data.chunk.text ?? '');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Say which tool is running.
|
|
107
|
+
*
|
|
108
|
+
* These events were previously dropped along with the reasoning deltas, on
|
|
109
|
+
* the grounds that neither is an answer. That is true of the content and
|
|
110
|
+
* wrong about the need: during a long tool call it is the only sign the
|
|
111
|
+
* agent is alive.
|
|
112
|
+
*/
|
|
113
|
+
async onToolCall(sessionId, event) {
|
|
114
|
+
const entry = this.active.get(sessionId);
|
|
115
|
+
if (!entry || entry.turn !== event.data.turn)
|
|
116
|
+
return;
|
|
117
|
+
await entry.stream.showActivity(describeToolCall(event.data.name, event.data.arguments));
|
|
118
|
+
}
|
|
119
|
+
/** Clear the activity line once the tool has answered. */
|
|
120
|
+
async onToolResult(sessionId, event) {
|
|
121
|
+
const entry = this.active.get(sessionId);
|
|
122
|
+
if (!entry || entry.turn !== event.data.turn)
|
|
123
|
+
return;
|
|
124
|
+
await entry.stream.showActivity(undefined);
|
|
125
|
+
}
|
|
126
|
+
/** Record the authoritative text for the turn. */
|
|
127
|
+
onMessage(sessionId, event) {
|
|
128
|
+
const entry = this.active.get(sessionId);
|
|
129
|
+
if (!entry || entry.turn !== event.data.turn)
|
|
130
|
+
return;
|
|
131
|
+
const text = (event.data.message.content ?? [])
|
|
132
|
+
.filter((block) => block.type === 'text')
|
|
133
|
+
.map((block) => block.text ?? '')
|
|
134
|
+
.join('');
|
|
135
|
+
if (text !== '')
|
|
136
|
+
entry.finalText = text;
|
|
137
|
+
}
|
|
138
|
+
/** Deliver the finished reply, or say why there is none. */
|
|
139
|
+
async onTurnEnd(sessionId, event) {
|
|
140
|
+
const entry = this.active.get(sessionId);
|
|
141
|
+
if (!entry || entry.turn !== event.data.turn)
|
|
142
|
+
return;
|
|
143
|
+
this.active.delete(sessionId);
|
|
144
|
+
entry.release();
|
|
145
|
+
const reason = event.data.reason;
|
|
146
|
+
if (reason?.kind === 'error') {
|
|
147
|
+
const failure = reason.error ?? { message: 'the turn failed' };
|
|
148
|
+
// Whatever streamed is kept: a turn can fail after saying something
|
|
149
|
+
// useful, and discarding it would lose the only answer there was.
|
|
150
|
+
await entry.stream.fail(new Error(failure.message));
|
|
151
|
+
this.options.onFailure?.(sessionId, failure);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
await entry.stream.finish(entry.finalText);
|
|
155
|
+
}
|
|
156
|
+
/** Finish a stream left open by a turn that never ended. */
|
|
157
|
+
async closeActive(sessionId) {
|
|
158
|
+
const entry = this.active.get(sessionId);
|
|
159
|
+
if (!entry)
|
|
160
|
+
return;
|
|
161
|
+
this.active.delete(sessionId);
|
|
162
|
+
entry.release();
|
|
163
|
+
await entry.stream.finish(entry.finalText).catch(() => undefined);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A draft id for one turn of one session.
|
|
168
|
+
*
|
|
169
|
+
* Telegram animates frames sharing an id, so it must be stable across a turn
|
|
170
|
+
* and different between turns — otherwise a new reply would animate out of the
|
|
171
|
+
* previous one. Derived rather than counted so a restart mid-conversation
|
|
172
|
+
* cannot collide with an id already in flight.
|
|
173
|
+
*/
|
|
174
|
+
function draftIdFor(sessionId, turn) {
|
|
175
|
+
let hash = 0;
|
|
176
|
+
for (let index = 0; index < sessionId.length; index += 1) {
|
|
177
|
+
hash = (hash * 31 + sessionId.charCodeAt(index)) | 0;
|
|
178
|
+
}
|
|
179
|
+
// Non-zero and inside the safe integer range Telegram accepts.
|
|
180
|
+
return Math.abs(hash % 1_000_000_000) * 1000 + (turn % 1000) + 1;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=turn-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-bridge.js","sourceRoot":"","sources":["../../src/reply/turn-bridge.ts"],"names":[],"mappings":"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"}
|