@llblab/pi-telegram 0.11.1 → 0.12.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/AGENTS.md +17 -12
- package/BACKLOG.md +0 -10
- package/CHANGELOG.md +33 -2
- package/README.md +42 -25
- package/api/inbound.ts +14 -0
- package/api/keyboard.ts +10 -0
- package/api/outbound.ts +11 -0
- package/api/sections.ts +17 -0
- package/api/updates.ts +11 -0
- package/api/voice.ts +24 -0
- package/docs/README.md +7 -5
- package/docs/architecture.md +176 -135
- package/docs/callback-namespaces.md +3 -3
- package/docs/command-templates.md +81 -24
- package/docs/{inbound-handlers.md → inbound.md} +13 -10
- package/docs/locks.md +3 -3
- package/docs/{outbound-handlers.md → outbound.md} +13 -10
- package/docs/public-api.md +266 -0
- package/docs/{extension-sections.md → sections.md} +31 -27
- package/docs/ui-style.md +165 -0
- package/docs/{external-handlers.md → updates.md} +33 -31
- package/docs/voice.md +17 -14
- package/index.ts +86 -261
- package/lib/bindings.ts +301 -0
- package/lib/command-templates.ts +163 -32
- package/lib/commands.ts +114 -1
- package/lib/config.ts +45 -4
- package/lib/{inbound-handlers.ts → inbound.ts} +5 -4
- package/lib/lifecycle.ts +122 -1
- package/lib/menu-model.ts +3 -3
- package/lib/menu-queue.ts +1 -1
- package/lib/menu-settings.ts +63 -32
- package/lib/menu-status.ts +1 -1
- package/lib/menu.ts +1 -1
- package/lib/{outbound-handlers.ts → outbound.ts} +21 -11
- package/lib/pi.ts +4 -0
- package/lib/polling.ts +4 -3
- package/lib/preview.ts +1 -1
- package/lib/routing.ts +45 -13
- package/lib/{extension-sections.ts → sections.ts} +37 -8
- package/lib/time-injection.ts +1 -1
- package/lib/updates.ts +121 -1
- package/lib/voice.ts +33 -14
- package/package.json +11 -1
- package/lib/external-handlers.ts +0 -166
package/lib/updates.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram updates domain helpers
|
|
3
3
|
* Zones: telegram inbound, authorization, routing plans
|
|
4
|
-
* Owns update extraction, authorization, classification, execution planning,
|
|
4
|
+
* Owns update extraction, authorization, classification, execution planning, runtime execution, and the public update-handler registry
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -851,3 +851,123 @@ export async function executeTelegramUpdatePlan<
|
|
|
851
851
|
if (!isTelegramStaleContextError(error)) throw error;
|
|
852
852
|
}
|
|
853
853
|
}
|
|
854
|
+
|
|
855
|
+
// --- Public update handler registry ---
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Verdict returned by a public Telegram update handler.
|
|
859
|
+
*
|
|
860
|
+
* - `"consume"` — the handler processed this update; pi-telegram skips default routing.
|
|
861
|
+
* - `"pass"` (or `void`/`undefined`) — pi-telegram routes the update normally.
|
|
862
|
+
*/
|
|
863
|
+
export type TelegramUpdateHandlerVerdict = "consume" | "pass";
|
|
864
|
+
|
|
865
|
+
export type TelegramUpdateHandler = (
|
|
866
|
+
update: unknown,
|
|
867
|
+
) =>
|
|
868
|
+
| TelegramUpdateHandlerVerdict
|
|
869
|
+
| void
|
|
870
|
+
| Promise<TelegramUpdateHandlerVerdict | void>;
|
|
871
|
+
|
|
872
|
+
export interface TelegramUpdateHandlerRegistry {
|
|
873
|
+
/** Schema version of this registry shape. */
|
|
874
|
+
readonly version: 1;
|
|
875
|
+
/**
|
|
876
|
+
* Register an update handler. Returns a disposer that removes it.
|
|
877
|
+
*
|
|
878
|
+
* Handlers are invoked in registration order on every Telegram update,
|
|
879
|
+
* before pi-telegram's own routing. The first handler that returns
|
|
880
|
+
* `"consume"` wins and stops the chain for that update.
|
|
881
|
+
*/
|
|
882
|
+
add: (handler: TelegramUpdateHandler) => () => void;
|
|
883
|
+
/**
|
|
884
|
+
* Run all registered handlers against an update.
|
|
885
|
+
*
|
|
886
|
+
* Used by pi-telegram's polling runtime; companion extensions should call
|
|
887
|
+
* {@link registerTelegramUpdateHandler} or `add` instead of dispatching directly.
|
|
888
|
+
*/
|
|
889
|
+
dispatch: (update: unknown) => Promise<TelegramUpdateHandlerVerdict>;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const UPDATE_HANDLER_REGISTRY_KEY = "__piTelegramUpdateHandlerRegistry__";
|
|
893
|
+
|
|
894
|
+
function isValidV1UpdateHandlerRegistry(
|
|
895
|
+
candidate: unknown,
|
|
896
|
+
): candidate is TelegramUpdateHandlerRegistry {
|
|
897
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
898
|
+
const r = candidate as Partial<TelegramUpdateHandlerRegistry>;
|
|
899
|
+
return (
|
|
900
|
+
r.version === 1 &&
|
|
901
|
+
typeof r.add === "function" &&
|
|
902
|
+
typeof r.dispatch === "function"
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function getOrCreateUpdateHandlerRegistry(): TelegramUpdateHandlerRegistry {
|
|
907
|
+
const g = globalThis as Record<string, unknown>;
|
|
908
|
+
const existing = g[UPDATE_HANDLER_REGISTRY_KEY];
|
|
909
|
+
if (isValidV1UpdateHandlerRegistry(existing)) return existing;
|
|
910
|
+
const handlers = new Set<TelegramUpdateHandler>();
|
|
911
|
+
const registry: TelegramUpdateHandlerRegistry = {
|
|
912
|
+
version: 1,
|
|
913
|
+
add(handler) {
|
|
914
|
+
handlers.add(handler);
|
|
915
|
+
return () => handlers.delete(handler);
|
|
916
|
+
},
|
|
917
|
+
async dispatch(update) {
|
|
918
|
+
for (const handler of handlers) {
|
|
919
|
+
try {
|
|
920
|
+
const result = await handler(update);
|
|
921
|
+
if (result === "consume") return "consume";
|
|
922
|
+
} catch {
|
|
923
|
+
// Update handler errors must not break polling.
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return "pass";
|
|
927
|
+
},
|
|
928
|
+
};
|
|
929
|
+
g[UPDATE_HANDLER_REGISTRY_KEY] = registry;
|
|
930
|
+
return registry;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Called by pi-telegram's own runtime to obtain the registry it dispatches
|
|
935
|
+
* through. Companion extensions should not call this; use
|
|
936
|
+
* {@link registerTelegramUpdateHandler} instead.
|
|
937
|
+
*/
|
|
938
|
+
export function getTelegramUpdateHandlerRegistry(): TelegramUpdateHandlerRegistry {
|
|
939
|
+
return getOrCreateUpdateHandlerRegistry();
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
export interface TelegramUpdateHandlerWrapDeps<TUpdate, TContext> {
|
|
943
|
+
defaultHandle: (update: TUpdate, ctx: TContext) => Promise<void>;
|
|
944
|
+
registry?: TelegramUpdateHandlerRegistry;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Wrap a default polling `handleUpdate` with the public update handler registry.
|
|
949
|
+
*/
|
|
950
|
+
export function createTelegramUpdateHandle<TUpdate, TContext>(
|
|
951
|
+
deps: TelegramUpdateHandlerWrapDeps<TUpdate, TContext>,
|
|
952
|
+
): (update: TUpdate, ctx: TContext) => Promise<void> {
|
|
953
|
+
const registry = deps.registry ?? getOrCreateUpdateHandlerRegistry();
|
|
954
|
+
const { defaultHandle } = deps;
|
|
955
|
+
return async function handleTelegramUpdate(update, ctx) {
|
|
956
|
+
const verdict = await registry.dispatch(update);
|
|
957
|
+
if (verdict === "consume") return;
|
|
958
|
+
await defaultHandle(update, ctx);
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Register a handler that runs before pi-telegram routes a Telegram update
|
|
964
|
+
* through its built-in handlers.
|
|
965
|
+
*
|
|
966
|
+
* This is the low-level public surface for companion extensions that share
|
|
967
|
+
* the same bot and pi process with pi-telegram.
|
|
968
|
+
*/
|
|
969
|
+
export function registerTelegramUpdateHandler(
|
|
970
|
+
handler: TelegramUpdateHandler,
|
|
971
|
+
): () => void {
|
|
972
|
+
return getOrCreateUpdateHandlerRegistry().add(handler);
|
|
973
|
+
}
|
package/lib/voice.ts
CHANGED
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Separation of concerns:
|
|
14
14
|
* - All decision logic and domain rules live here.
|
|
15
|
-
* - Actual delivery (sending the audio via Telegram) stays in outbound
|
|
15
|
+
* - Actual delivery (sending the audio via Telegram) stays in outbound.ts.
|
|
16
16
|
*
|
|
17
17
|
* Keeps voice policy, turn tagging, prompt contributions, and markup helpers
|
|
18
18
|
* out of the queue, preview, turn-building, and delivery domains.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
const VOICE_SYNTHESIS_PROVIDER_REGISTRY_KEY =
|
|
21
|
+
const VOICE_SYNTHESIS_PROVIDER_REGISTRY_KEY =
|
|
22
|
+
"__piTelegramVoiceSynthesisProviders__";
|
|
22
23
|
const VOICE_TRANSCRIPTION_PROVIDER_REGISTRY_KEY =
|
|
23
24
|
"__piTelegramVoiceTranscriptionProviders__";
|
|
24
25
|
|
|
@@ -81,11 +82,20 @@ function getOrCreateVoiceSynthesisProviderRegistry(): Map<
|
|
|
81
82
|
if (existing instanceof Map)
|
|
82
83
|
return existing as Map<string, TelegramVoiceSynthesisProvider>;
|
|
83
84
|
const registry = new Map<string, TelegramVoiceSynthesisProvider>();
|
|
84
|
-
(globalThis as Record<string, unknown>)[
|
|
85
|
-
|
|
85
|
+
(globalThis as Record<string, unknown>)[
|
|
86
|
+
VOICE_SYNTHESIS_PROVIDER_REGISTRY_KEY
|
|
87
|
+
] = registry;
|
|
86
88
|
return registry;
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Register a high-level Telegram voice synthesis provider.
|
|
93
|
+
*
|
|
94
|
+
* Stable public API callers must pass a stable `options.id` so diagnostics,
|
|
95
|
+
* replacement, and cleanup can identify the provider. Omitted ids remain a
|
|
96
|
+
* compatibility path for pre-matrix callers and receive generated session-local
|
|
97
|
+
* ids.
|
|
98
|
+
*/
|
|
89
99
|
export function registerTelegramVoiceSynthesisProvider(
|
|
90
100
|
provider:
|
|
91
101
|
| TelegramVoiceSynthesisProvider
|
|
@@ -103,9 +113,11 @@ export function registerTelegramVoiceSynthesisProvider(
|
|
|
103
113
|
(text: string, options?: { lang?: string; rate?: string }) =>
|
|
104
114
|
provider(text, options),
|
|
105
115
|
{
|
|
106
|
-
getVoicePolicy: (provider as TelegramVoiceSynthesisProvider)
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
getVoicePolicy: (provider as TelegramVoiceSynthesisProvider)
|
|
117
|
+
.getVoicePolicy,
|
|
118
|
+
getVoicePromptContribution: (
|
|
119
|
+
provider as TelegramVoiceSynthesisProvider
|
|
120
|
+
).getVoicePromptContribution,
|
|
109
121
|
},
|
|
110
122
|
) as TelegramVoiceSynthesisProvider)
|
|
111
123
|
: provider;
|
|
@@ -144,6 +156,13 @@ function getOrCreateVoiceTranscriptionProviderRegistry(): Map<
|
|
|
144
156
|
return registry;
|
|
145
157
|
}
|
|
146
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Register a high-level Telegram voice transcription provider.
|
|
161
|
+
*
|
|
162
|
+
* Stable public API callers must pass a stable `options.id`. Omitted ids remain
|
|
163
|
+
* a compatibility path for pre-matrix callers and receive generated
|
|
164
|
+
* session-local ids.
|
|
165
|
+
*/
|
|
147
166
|
export function registerTelegramVoiceTranscriptionProvider(
|
|
148
167
|
provider: TelegramVoiceTranscriptionProvider,
|
|
149
168
|
options?: { id?: string },
|
|
@@ -182,9 +201,9 @@ export const TELEGRAM_VOICE_REPLY_MODES = [
|
|
|
182
201
|
* Pi-telegram owns reply-mode policy through telegram.json. If
|
|
183
202
|
* config.voice.replyMode is missing or invalid, the safe default is manual.
|
|
184
203
|
*/
|
|
185
|
-
export function getTelegramVoiceReplyMode(
|
|
186
|
-
|
|
187
|
-
): TelegramVoiceReplyMode {
|
|
204
|
+
export function getTelegramVoiceReplyMode(config?: {
|
|
205
|
+
voice?: { replyMode?: string };
|
|
206
|
+
}): TelegramVoiceReplyMode {
|
|
188
207
|
const configMode = config?.voice?.replyMode;
|
|
189
208
|
if (
|
|
190
209
|
configMode &&
|
|
@@ -202,9 +221,9 @@ export function getTelegramVoiceReplyMode(
|
|
|
202
221
|
* Reads from `config.voice.sendTranscript`.
|
|
203
222
|
* Default: false (no transcript text sent at all).
|
|
204
223
|
*/
|
|
205
|
-
export function getTelegramVoiceSendTranscript(
|
|
206
|
-
|
|
207
|
-
): boolean {
|
|
224
|
+
export function getTelegramVoiceSendTranscript(config?: {
|
|
225
|
+
voice?: { sendTranscript?: boolean };
|
|
226
|
+
}): boolean {
|
|
208
227
|
return !!config?.voice?.sendTranscript;
|
|
209
228
|
}
|
|
210
229
|
|
|
@@ -292,4 +311,4 @@ export {
|
|
|
292
311
|
stripTelegramCommentMarkupForDelivery,
|
|
293
312
|
stripTelegramVoiceMarkupForPreview,
|
|
294
313
|
normalizeMarkdownAfterVoiceExtraction,
|
|
295
|
-
} from "./outbound
|
|
314
|
+
} from "./outbound.ts";
|
package/package.json
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-telegram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"description": "Telegram runtime adapter for π",
|
|
9
9
|
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./index.ts",
|
|
12
|
+
"./inbound": "./api/inbound.ts",
|
|
13
|
+
"./outbound": "./api/outbound.ts",
|
|
14
|
+
"./updates": "./api/updates.ts",
|
|
15
|
+
"./sections": "./api/sections.ts",
|
|
16
|
+
"./voice": "./api/voice.ts",
|
|
17
|
+
"./keyboard": "./api/keyboard.ts"
|
|
18
|
+
},
|
|
10
19
|
"keywords": [
|
|
11
20
|
"pi-package",
|
|
12
21
|
"pi",
|
|
@@ -35,6 +44,7 @@
|
|
|
35
44
|
},
|
|
36
45
|
"files": [
|
|
37
46
|
"index.ts",
|
|
47
|
+
"api/",
|
|
38
48
|
"lib/",
|
|
39
49
|
"README.md",
|
|
40
50
|
"AGENTS.md",
|
package/lib/external-handlers.ts
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External Telegram handler registry
|
|
3
|
-
* Zones: telegram transport, layered extension interop
|
|
4
|
-
* Lets other pi extensions hook into the polling loop without owning their own getUpdates connection
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Verdict returned by an interceptor.
|
|
9
|
-
*
|
|
10
|
-
* - `"consume"` — the interceptor handled this update; pi-telegram skips default routing.
|
|
11
|
-
* - `"pass"` (or `void`/`undefined`) — pi-telegram routes the update normally.
|
|
12
|
-
*/
|
|
13
|
-
export type TelegramExternalHandlerVerdict = "consume" | "pass";
|
|
14
|
-
|
|
15
|
-
export type TelegramExternalHandler = (
|
|
16
|
-
update: unknown,
|
|
17
|
-
) =>
|
|
18
|
-
| TelegramExternalHandlerVerdict
|
|
19
|
-
| void
|
|
20
|
-
| Promise<TelegramExternalHandlerVerdict | void>;
|
|
21
|
-
|
|
22
|
-
export interface TelegramExternalHandlerRegistry {
|
|
23
|
-
/** Schema version of this registry shape. */
|
|
24
|
-
readonly version: 1;
|
|
25
|
-
/**
|
|
26
|
-
* Register an interceptor. Returns a disposer that removes it.
|
|
27
|
-
*
|
|
28
|
-
* Interceptors are invoked in registration order on every Telegram update,
|
|
29
|
-
* before pi-telegram's own routing. The first interceptor that returns
|
|
30
|
-
* `"consume"` wins and stops the chain for that update.
|
|
31
|
-
*/
|
|
32
|
-
add: (handler: TelegramExternalHandler) => () => void;
|
|
33
|
-
/**
|
|
34
|
-
* Run all registered interceptors against an update.
|
|
35
|
-
*
|
|
36
|
-
* Used by pi-telegram's polling runtime; layered extensions should call
|
|
37
|
-
* {@link onTelegramExternalUpdate} or `add` instead of dispatching directly.
|
|
38
|
-
*/
|
|
39
|
-
dispatch: (update: unknown) => Promise<TelegramExternalHandlerVerdict>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const REGISTRY_KEY = "__piTelegramExternalHandlerRegistry__";
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Validate that a value on `globalThis` matches the full v1 registry contract.
|
|
46
|
-
*
|
|
47
|
-
* pi-telegram's polling runtime invokes `dispatch`, so a partial object that
|
|
48
|
-
* only carries `version` and `add` (which an early draft of the zero-coupling
|
|
49
|
-
* docs showed) would silently break the first update. We treat any object
|
|
50
|
-
* tagged `version === 1` but missing required methods as malformed and
|
|
51
|
-
* replace it with a fresh, fully-formed registry. Layered extensions that
|
|
52
|
-
* follow the full documented shape are unaffected; ones that don't lose any
|
|
53
|
-
* handlers they registered against the malformed object, which is the
|
|
54
|
-
* desired fail-loud-during-development behavior.
|
|
55
|
-
*/
|
|
56
|
-
function isValidV1Registry(
|
|
57
|
-
candidate: unknown,
|
|
58
|
-
): candidate is TelegramExternalHandlerRegistry {
|
|
59
|
-
if (!candidate || typeof candidate !== "object") return false;
|
|
60
|
-
const r = candidate as Partial<TelegramExternalHandlerRegistry>;
|
|
61
|
-
return (
|
|
62
|
-
r.version === 1 &&
|
|
63
|
-
typeof r.add === "function" &&
|
|
64
|
-
typeof r.dispatch === "function"
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function getOrCreateRegistry(): TelegramExternalHandlerRegistry {
|
|
69
|
-
const g = globalThis as Record<string, unknown>;
|
|
70
|
-
const existing = g[REGISTRY_KEY];
|
|
71
|
-
if (isValidV1Registry(existing)) return existing;
|
|
72
|
-
const handlers = new Set<TelegramExternalHandler>();
|
|
73
|
-
const registry: TelegramExternalHandlerRegistry = {
|
|
74
|
-
version: 1,
|
|
75
|
-
add(handler) {
|
|
76
|
-
handlers.add(handler);
|
|
77
|
-
return () => handlers.delete(handler);
|
|
78
|
-
},
|
|
79
|
-
async dispatch(update) {
|
|
80
|
-
for (const handler of handlers) {
|
|
81
|
-
try {
|
|
82
|
-
const result = await handler(update);
|
|
83
|
-
if (result === "consume") return "consume";
|
|
84
|
-
} catch {
|
|
85
|
-
// External handler errors must not break polling.
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return "pass";
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
g[REGISTRY_KEY] = registry;
|
|
92
|
-
return registry;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Called by pi-telegram's own runtime to obtain the registry it dispatches
|
|
97
|
-
* through. Layered extensions should not call this; use
|
|
98
|
-
* {@link onTelegramExternalUpdate} instead.
|
|
99
|
-
*/
|
|
100
|
-
export function getTelegramExternalHandlerRegistry(): TelegramExternalHandlerRegistry {
|
|
101
|
-
return getOrCreateRegistry();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface TelegramExternalHandlerWrapDeps<TUpdate, TContext> {
|
|
105
|
-
defaultHandle: (update: TUpdate, ctx: TContext) => Promise<void>;
|
|
106
|
-
registry?: TelegramExternalHandlerRegistry;
|
|
107
|
-
}
|
|
108
|
-
export type TelegramExternalInterceptorWrapDeps<TUpdate, TContext> =
|
|
109
|
-
TelegramExternalHandlerWrapDeps<TUpdate, TContext>;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Wrap a default polling `handleUpdate` with the external interceptor registry.
|
|
113
|
-
*
|
|
114
|
-
* Returned function dispatches `update` through registered interceptors first;
|
|
115
|
-
* if any returns `"consume"`, default routing is skipped for that update.
|
|
116
|
-
*
|
|
117
|
-
* Composition-root callers (pi-telegram's `index.ts`) should use this builder
|
|
118
|
-
* instead of writing the lifting logic inline.
|
|
119
|
-
*/
|
|
120
|
-
export function createTelegramExternalHandleUpdate<TUpdate, TContext>(
|
|
121
|
-
deps: TelegramExternalHandlerWrapDeps<TUpdate, TContext>,
|
|
122
|
-
): (update: TUpdate, ctx: TContext) => Promise<void> {
|
|
123
|
-
const registry = deps.registry ?? getOrCreateRegistry();
|
|
124
|
-
const { defaultHandle } = deps;
|
|
125
|
-
return async function handleInterceptedUpdate(update, ctx) {
|
|
126
|
-
const verdict = await registry.dispatch(update);
|
|
127
|
-
if (verdict === "consume") return;
|
|
128
|
-
await defaultHandle(update, ctx);
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Register an interceptor that runs before pi-telegram routes a Telegram
|
|
134
|
-
* update through its built-in handlers (commands, app menu, queue menu,
|
|
135
|
-
* model menu, default prompt routing).
|
|
136
|
-
*
|
|
137
|
-
* This is the recommended public surface for layered extensions that share
|
|
138
|
-
* the same bot and pi process with pi-telegram (single bot ↔ single
|
|
139
|
-
* `getUpdates` poller).
|
|
140
|
-
*
|
|
141
|
-
* Returns a disposer that removes the interceptor.
|
|
142
|
-
*
|
|
143
|
-
* @example
|
|
144
|
-
* ```ts
|
|
145
|
-
* import { onTelegramExternalUpdate } from "@llblab/pi-telegram/lib/external-handlers.ts";
|
|
146
|
-
*
|
|
147
|
-
* const off = onTelegramExternalUpdate(async (update) => {
|
|
148
|
-
* const cb = (update as { callback_query?: { data?: string } }).callback_query;
|
|
149
|
-
* if (!cb?.data?.startsWith("myext:")) return "pass";
|
|
150
|
-
* await handleMyCallback(cb);
|
|
151
|
-
* return "consume"; // skip pi-telegram's default routing for this update
|
|
152
|
-
* });
|
|
153
|
-
*
|
|
154
|
-
* // later, e.g. on session shutdown:
|
|
155
|
-
* off();
|
|
156
|
-
* ```
|
|
157
|
-
*
|
|
158
|
-
* Extensions that prefer zero coupling can also reach the versioned registry
|
|
159
|
-
* directly on `globalThis`. This avoids importing `@llblab/pi-telegram` and
|
|
160
|
-
* tolerates either install order.
|
|
161
|
-
*/
|
|
162
|
-
export function onTelegramExternalUpdate(
|
|
163
|
-
handler: TelegramExternalHandler,
|
|
164
|
-
): () => void {
|
|
165
|
-
return getOrCreateRegistry().add(handler);
|
|
166
|
-
}
|