@llblab/pi-telegram 0.27.12 → 0.29.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.
@@ -294,6 +294,32 @@ export interface TelegramActivityVerbosityRuntime {
294
294
  waitForIdle: () => Promise<void>;
295
295
  }
296
296
 
297
+ export interface TelegramActivityVerbosityBinding
298
+ extends TelegramActivityVerbosityRuntime {
299
+ bind: (runtime: TelegramActivityVerbosityRuntime) => void;
300
+ }
301
+
302
+ export function createTelegramActivityVerbosityBinding(): TelegramActivityVerbosityBinding {
303
+ let runtime: TelegramActivityVerbosityRuntime | undefined;
304
+ return {
305
+ bind(next) {
306
+ runtime = next;
307
+ },
308
+ accept(event) {
309
+ runtime?.accept(event);
310
+ },
311
+ reset() {
312
+ runtime?.reset();
313
+ },
314
+ stop() {
315
+ runtime?.stop();
316
+ },
317
+ waitForIdle() {
318
+ return runtime?.waitForIdle() ?? Promise.resolve();
319
+ },
320
+ };
321
+ }
322
+
297
323
  export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
298
324
  getActivityMode: () => "quiet" | "thinking" | "tools" | "verbose";
299
325
  refreshActivityMode?: () => Promise<void>;
package/lib/bindings.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import * as Activity from "./activity.ts";
8
- import type { TelegramActivityVerbosityRuntime } from "./activity-verbosity.ts";
8
+ import * as ActivityVerbosity from "./activity-verbosity.ts";
9
9
  import * as CommandTemplates from "./command-templates.ts";
10
10
  import * as Commands from "./commands.ts";
11
11
  import * as Config from "./config.ts";
@@ -25,6 +25,7 @@ import * as Runtime from "./runtime.ts";
25
25
  import * as Setup from "./setup.ts";
26
26
  import * as Status from "./status.ts";
27
27
  import * as TelegramApi from "./telegram-api.ts";
28
+ import type { TelegramTarget } from "./target.ts";
28
29
 
29
30
  type ActivePiModel = NonNullable<Pi.ExtensionContext["model"]>;
30
31
 
@@ -37,6 +38,167 @@ type TelegramRuntimeEventRecorder = (
37
38
  type TelegramBridgeStatusUpdater =
38
39
  Status.TelegramStatusRuntime<Pi.ExtensionContext>["updateStatus"];
39
40
 
41
+ type TelegramAgentTargetResolver = NonNullable<
42
+ OutboundAttachments.TelegramOutboundMessageToolRegistrationDeps["resolveAgentTarget"]
43
+ >;
44
+ type TelegramAgentMessageRouter = NonNullable<
45
+ OutboundAttachments.TelegramOutboundMessageToolRegistrationDeps["routeAgentMessage"]
46
+ >;
47
+
48
+ export interface TelegramQueueBindingRuntime<TContext> {
49
+ mutation: Queue.TelegramQueueMutationController<TContext>;
50
+ dispatchNext: (ctx: TContext) => void;
51
+ watchdog: Queue.TelegramQueueDispatchWatchdogRuntime<TContext>;
52
+ }
53
+
54
+ export function createTelegramQueueBindingRuntime<TContext>(deps: {
55
+ store: Queue.TelegramQueueStateStore<TContext>;
56
+ queue: Pick<
57
+ Runtime.TelegramBridgeRuntime["queue"],
58
+ "getNextPriorityReactionOrder" | "incrementNextPriorityReactionOrder"
59
+ >;
60
+ lifecycle: Pick<
61
+ Runtime.TelegramBridgeRuntime["lifecycle"],
62
+ "isCompactionInProgress" | "hasDispatchPending"
63
+ >;
64
+ activeTurn: Pick<Queue.TelegramActiveTurnStore, "has">;
65
+ admission: {
66
+ getSettlement: () =>
67
+ | {
68
+ onItemsDiscarded: (
69
+ items: readonly Queue.TelegramQueueItem<TContext>[],
70
+ ctx: TContext,
71
+ ) => void;
72
+ isItemReady: (item: Queue.TelegramQueueItem<TContext>) => boolean;
73
+ onControlSettled: (
74
+ item: Queue.PendingTelegramControlItem<TContext>,
75
+ ctx: TContext,
76
+ ) => void;
77
+ }
78
+ | undefined;
79
+ hasPendingQueueMutationForItem: (
80
+ item: Queue.TelegramQueueItem<TContext>,
81
+ ) => boolean;
82
+ };
83
+ transportStamp: Pick<Queue.TelegramTransportStampRuntime, "isActive">;
84
+ deferredDispatch: Pick<
85
+ Queue.TelegramDeferredQueueDispatchRuntime<TContext>,
86
+ "isBound" | "getGeneration" | "isGenerationActive"
87
+ >;
88
+ promptDispatch: Runtime.TelegramPromptDispatchRuntime<TContext>;
89
+ isIdle: (ctx: TContext) => boolean;
90
+ hasPendingMessages: (ctx: TContext) => boolean;
91
+ updateStatus: (ctx: TContext, error?: string) => void;
92
+ sendTextReply: Queue.TelegramQueueDispatchRuntimeDeps<TContext>["sendTextReply"];
93
+ sendUserMessage: Queue.TelegramQueueDispatchRuntimeDeps<TContext>["sendUserMessage"];
94
+ recordRuntimeEvent?: TelegramRuntimeEventRecorder;
95
+ }): TelegramQueueBindingRuntime<TContext> {
96
+ const mutation = Queue.createTelegramQueueMutationController({
97
+ ...deps.store,
98
+ getNextPriorityReactionOrder: deps.queue.getNextPriorityReactionOrder,
99
+ incrementNextPriorityReactionOrder:
100
+ deps.queue.incrementNextPriorityReactionOrder,
101
+ onItemsDiscarded(items, ctx) {
102
+ deps.admission.getSettlement()?.onItemsDiscarded(items, ctx);
103
+ },
104
+ updateStatus: deps.updateStatus,
105
+ recordRuntimeEvent: deps.recordRuntimeEvent,
106
+ });
107
+ const dispatchNext = Queue.createTelegramQueueDispatchRuntime({
108
+ ...deps.store,
109
+ isCompactionInProgress: deps.lifecycle.isCompactionInProgress,
110
+ hasActiveTurn: deps.activeTurn.has,
111
+ hasDispatchPending: deps.lifecycle.hasDispatchPending,
112
+ isIdle: deps.isIdle,
113
+ hasPendingMessages: deps.hasPendingMessages,
114
+ hasDispatchContext: deps.deferredDispatch.isBound,
115
+ getDispatchGeneration: deps.deferredDispatch.getGeneration,
116
+ isDispatchGenerationActive: deps.deferredDispatch.isGenerationActive,
117
+ isQueueItemTransportActive(item) {
118
+ return deps.transportStamp.isActive(item.transportStamp);
119
+ },
120
+ hasPendingInboundQueueMutationForItem(item) {
121
+ return deps.admission.hasPendingQueueMutationForItem(item);
122
+ },
123
+ isQueueItemAdmissionReady(item) {
124
+ return (
125
+ deps.admission.getSettlement()?.isItemReady(item) ??
126
+ (item.admissionReceipts?.length ?? 0) === 0
127
+ );
128
+ },
129
+ onControlSettled(item, ctx) {
130
+ deps.admission.getSettlement()?.onControlSettled(item, ctx);
131
+ },
132
+ updateStatus: deps.updateStatus,
133
+ sendTextReply: deps.sendTextReply,
134
+ recordRuntimeEvent: deps.recordRuntimeEvent,
135
+ ...deps.promptDispatch,
136
+ sendUserMessage: deps.sendUserMessage,
137
+ }).dispatchNext;
138
+ return {
139
+ mutation,
140
+ dispatchNext,
141
+ watchdog: Queue.createTelegramQueueDispatchWatchdogRuntime({
142
+ hasQueuedItems: deps.store.hasQueuedItems,
143
+ dispatchNextQueuedTelegramTurn: dispatchNext,
144
+ recordRuntimeEvent: deps.recordRuntimeEvent,
145
+ }),
146
+ };
147
+ }
148
+
149
+ export interface TelegramAgentMessageToolRoutingRuntime {
150
+ resolveAgentTarget: TelegramAgentTargetResolver;
151
+ routeAgentMessage: TelegramAgentMessageRouter;
152
+ canSendDirect: () => boolean;
153
+ }
154
+
155
+ export function createTelegramAgentMessageToolRoutingRuntime(deps: {
156
+ ownsLeader: () => boolean;
157
+ ownsDirectDelivery: () => boolean;
158
+ isFollowerRegistered: () => boolean;
159
+ getSourceTarget: () => TelegramTarget | undefined;
160
+ getSourceThreadName: () => string | undefined;
161
+ local: {
162
+ resolveTarget: (
163
+ selector: Parameters<TelegramAgentTargetResolver>[0],
164
+ sourceTarget?: TelegramTarget,
165
+ ) => Awaited<ReturnType<TelegramAgentTargetResolver>> | undefined;
166
+ route: (input: {
167
+ sourceTarget?: TelegramTarget;
168
+ sourceThreadName?: string;
169
+ message: Parameters<TelegramAgentMessageRouter>[0];
170
+ }) => Promise<void>;
171
+ };
172
+ follower: {
173
+ resolveTarget: TelegramAgentTargetResolver;
174
+ routeMessage: TelegramAgentMessageRouter;
175
+ };
176
+ }): TelegramAgentMessageToolRoutingRuntime {
177
+ return {
178
+ async resolveAgentTarget(selector) {
179
+ if (!deps.ownsLeader()) return deps.follower.resolveTarget(selector);
180
+ const target = deps.local.resolveTarget(selector, deps.getSourceTarget());
181
+ if (!target) {
182
+ throw new Error(
183
+ "Telegram agent target is unavailable, ambiguous, or not live.",
184
+ );
185
+ }
186
+ return target;
187
+ },
188
+ async routeAgentMessage(message) {
189
+ if (!deps.ownsLeader()) return deps.follower.routeMessage(message);
190
+ await deps.local.route({
191
+ sourceTarget: deps.getSourceTarget(),
192
+ sourceThreadName: deps.getSourceThreadName(),
193
+ message,
194
+ });
195
+ },
196
+ canSendDirect() {
197
+ return deps.ownsDirectDelivery() || deps.isFollowerRegistered();
198
+ },
199
+ };
200
+ }
201
+
40
202
  export interface TelegramAssistantOutputBindingRuntime<TTransportStamp> {
41
203
  runtime: Activity.TelegramAssistantOutputRuntime;
42
204
  observeEvent: (event: Activity.TelegramActivityEvent) => void;
@@ -97,6 +259,76 @@ export function createTelegramAssistantOutputBindingRuntime<
97
259
  };
98
260
  }
99
261
 
262
+ type TelegramAssistantOutputAuthority<TTransportStamp> = ReturnType<
263
+ Routing.TelegramAssistantOutputAuthorityRuntime<TTransportStamp>["captureAuthority"]
264
+ >;
265
+
266
+ export interface TelegramActivityBindingRuntime {
267
+ activityRuntime: Activity.TelegramActivityRuntime;
268
+ activityVerbosityRuntime: ActivityVerbosity.TelegramActivityVerbosityRuntime;
269
+ assistantOutputRuntime: Activity.TelegramAssistantOutputRuntime;
270
+ }
271
+
272
+ /** Compose public activity fanout, verbosity, and assistant output ordering. */
273
+ export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
274
+ generation: string;
275
+ assistantOutput: Omit<
276
+ Parameters<
277
+ typeof createTelegramAssistantOutputBindingRuntime<TTransportStamp>
278
+ >[0],
279
+ "waitForActivityIdle"
280
+ >;
281
+ activityVerbosity: Omit<
282
+ Parameters<
283
+ typeof ActivityVerbosity.createTelegramActivityVerbosityRuntime<
284
+ TelegramAssistantOutputAuthority<TTransportStamp>
285
+ >
286
+ >[0],
287
+ "captureAuthority" | "isAuthorityActive" | "recordFailure"
288
+ >;
289
+ }): TelegramActivityBindingRuntime {
290
+ const activityVerbosityBinding =
291
+ ActivityVerbosity.createTelegramActivityVerbosityBinding();
292
+ const assistantOutputBinding =
293
+ createTelegramAssistantOutputBindingRuntime({
294
+ ...deps.assistantOutput,
295
+ waitForActivityIdle: activityVerbosityBinding.waitForIdle,
296
+ });
297
+ const activityVerbosityRuntime =
298
+ ActivityVerbosity.createTelegramActivityVerbosityRuntime({
299
+ ...deps.activityVerbosity,
300
+ captureAuthority: assistantOutputBinding.authority.captureAuthority,
301
+ isAuthorityActive: assistantOutputBinding.authority.isAuthorityActive,
302
+ recordFailure(operation, event, error) {
303
+ deps.assistantOutput.recordRuntimeEvent("activity", error, {
304
+ operation,
305
+ eventType: event.type,
306
+ activityId: event.activityId,
307
+ });
308
+ },
309
+ });
310
+ activityVerbosityBinding.bind(activityVerbosityRuntime);
311
+ const activityRuntime = Activity.createTelegramActivityBridgeRuntime({
312
+ generation: deps.generation,
313
+ observeEvent(event) {
314
+ assistantOutputBinding.observeEvent(event);
315
+ activityVerbosityRuntime.accept(event);
316
+ },
317
+ recordFailure(handlerId, event, error) {
318
+ deps.assistantOutput.recordRuntimeEvent("activity", error, {
319
+ handlerId,
320
+ eventType: event.type,
321
+ activityId: event.activityId,
322
+ });
323
+ },
324
+ });
325
+ return {
326
+ activityRuntime,
327
+ activityVerbosityRuntime,
328
+ assistantOutputRuntime: assistantOutputBinding.runtime,
329
+ };
330
+ }
331
+
100
332
  interface TelegramCommandsAndToolsBindingDeps {
101
333
  pi: Pi.ExtensionAPI;
102
334
  configStore: Config.TelegramConfigStore;
@@ -172,9 +404,6 @@ export function registerTelegramCommandsAndTools({
172
404
  sendMarkdownReply(chatId, undefined, markdown, options),
173
405
  recordRuntimeEvent,
174
406
  });
175
- Prompts.registerTelegramHelpTool(pi, {
176
- getActiveProfileName: configStore.getActiveProfileName,
177
- });
178
407
  Commands.registerTelegramBridgeCommands(pi, {
179
408
  promptForConfig: async (ctx, profileName) => {
180
409
  const nextProfileName = profileName ?? undefined;
@@ -291,7 +520,7 @@ export function registerTelegramCommandsAndTools({
291
520
  interface TelegramLifecycleBindingDeps {
292
521
  pi: Pi.ExtensionAPI;
293
522
  activityRuntime: Activity.TelegramActivityRuntime;
294
- activityVerbosityRuntime?: TelegramActivityVerbosityRuntime;
523
+ activityVerbosityRuntime?: ActivityVerbosity.TelegramActivityVerbosityRuntime;
295
524
  assistantOutputRuntime: Pick<
296
525
  Activity.TelegramAssistantOutputRuntime,
297
526
  "start" | "waitForIdle" | "stop"
@@ -344,6 +573,10 @@ interface TelegramLifecycleBindingDeps {
344
573
  >["sendTextReply"] &
345
574
  NonNullable<OutboundHandlers.TelegramVoiceReplySenderDeps["sendTextReply"]>;
346
575
  dispatchNextQueuedTelegramTurn: (ctx: Pi.ExtensionContext) => void;
576
+ onPromptHandedOff?: (
577
+ turn: Queue.PendingTelegramTurn,
578
+ ctx: Pi.ExtensionContext,
579
+ ) => void;
347
580
  answerGuestQuery: TelegramApi.TelegramBridgeApiRuntime["answerGuestQuery"];
348
581
  deleteMessage: TelegramApi.TelegramBridgeApiRuntime["deleteMessage"];
349
582
  sendGuestReply: NonNullable<
@@ -400,6 +633,7 @@ export function registerTelegramLifecycleRuntimeHooks({
400
633
  sendMarkdownReply,
401
634
  sendTextReply,
402
635
  dispatchNextQueuedTelegramTurn,
636
+ onPromptHandedOff,
403
637
  answerGuestQuery,
404
638
  deleteMessage,
405
639
  sendGuestReply,
@@ -554,6 +788,7 @@ export function registerTelegramLifecycleRuntimeHooks({
554
788
  clearDispatchPending: lifecycle.clearDispatchPending,
555
789
  setFoldQueuedPromptsIntoHistory: lifecycle.setFoldQueuedPromptsIntoHistory,
556
790
  setActiveTurn: activeTurnRuntime.set,
791
+ onPromptHandedOff,
557
792
  createPreviewState: previewRuntime.resetState,
558
793
  startTypingLoop: (ctx) => {
559
794
  const turn = activeTurnRuntime.get();