@dbx-tools/teams 0.3.44 → 0.4.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.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Interceptor defaults for the Teams plugin.
3
+ *
4
+ * Building a card is a pure transform, but it still runs through
5
+ * `Plugin.execute()` so it shares the app's telemetry / timeout posture; the
6
+ * one operation that does I/O is posting a card to a Teams incoming webhook.
7
+ * The settings are kept here rather than at the call sites so the caching /
8
+ * retry / timeout posture of each is reviewable in one place.
9
+ *
10
+ * @module
11
+ */
12
+ /**
13
+ * The `PluginExecuteConfig` slice this package sets. Mirrored structurally
14
+ * because AppKit's `PluginExecuteConfig` lives behind a subpath its `exports`
15
+ * map does not publish, so the nominal type cannot be imported. Written as a
16
+ * type alias rather than an interface so it stays assignable to the nominal
17
+ * type's index signature.
18
+ */
19
+ export type TeamsExecuteConfig = {
20
+ cache?: {
21
+ enabled?: boolean;
22
+ ttl?: number;
23
+ cacheKey?: (string | number | object)[];
24
+ };
25
+ retry?: {
26
+ enabled?: boolean;
27
+ attempts?: number;
28
+ initialDelay?: number;
29
+ maxDelay?: number;
30
+ };
31
+ timeout?: number;
32
+ };
33
+ /**
34
+ * The `PluginExecutionSettings` shape accepted by AppKit's `Plugin.execute()`.
35
+ * Mirrored structurally for the same reason as {@link TeamsExecuteConfig}.
36
+ */
37
+ export type TeamsExecutionSettings = {
38
+ default: TeamsExecuteConfig;
39
+ user?: TeamsExecuteConfig;
40
+ };
41
+ /** Ceiling on how long a single card build may take. */
42
+ export declare const BUILD_TIMEOUT_MS = 5000;
43
+ /** Ceiling on how long a single webhook POST may take. */
44
+ export declare const POST_TIMEOUT_MS = 15000;
45
+ /** Attempts allowed for a webhook POST, including the first. */
46
+ export declare const POST_ATTEMPTS = 3;
47
+ /** Execution settings for building a card (a pure, in-process transform). */
48
+ export declare const TEAMS_BUILD_SETTINGS: TeamsExecutionSettings;
49
+ /**
50
+ * Ceiling on one conversation turn. Generous relative to the other two: a turn
51
+ * spans a full agent call (model latency plus any tool the agent runs), so it is
52
+ * bounded by the same order of magnitude as a chat request rather than a
53
+ * transform.
54
+ */
55
+ export declare const TURN_TIMEOUT_MS = 120000;
56
+ /** Execution settings for one Teams conversation turn (an agent call). */
57
+ export declare const TEAMS_TURN_SETTINGS: TeamsExecutionSettings;
58
+ /** Execution settings for posting a card to a Teams incoming webhook. */
59
+ export declare const TEAMS_POST_SETTINGS: TeamsExecutionSettings;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Interceptor defaults for the Teams plugin.
3
+ *
4
+ * Building a card is a pure transform, but it still runs through
5
+ * `Plugin.execute()` so it shares the app's telemetry / timeout posture; the
6
+ * one operation that does I/O is posting a card to a Teams incoming webhook.
7
+ * The settings are kept here rather than at the call sites so the caching /
8
+ * retry / timeout posture of each is reviewable in one place.
9
+ *
10
+ * @module
11
+ */
12
+ /** Ceiling on how long a single card build may take. */
13
+ export const BUILD_TIMEOUT_MS = 5_000;
14
+ /** Ceiling on how long a single webhook POST may take. */
15
+ export const POST_TIMEOUT_MS = 15_000;
16
+ /** Attempts allowed for a webhook POST, including the first. */
17
+ export const POST_ATTEMPTS = 3;
18
+ /** Execution settings for building a card (a pure, in-process transform). */
19
+ export const TEAMS_BUILD_SETTINGS = {
20
+ default: {
21
+ // Cache disabled: the build is cheap and deterministic, so a cache would
22
+ // add a cross-identity key surface for no measurable saving.
23
+ cache: { enabled: false },
24
+ // Retry disabled: the transform performs no I/O, so a failure is
25
+ // deterministic and a second attempt would fail identically.
26
+ retry: { enabled: false },
27
+ timeout: BUILD_TIMEOUT_MS,
28
+ },
29
+ };
30
+ /**
31
+ * Ceiling on one conversation turn. Generous relative to the other two: a turn
32
+ * spans a full agent call (model latency plus any tool the agent runs), so it is
33
+ * bounded by the same order of magnitude as a chat request rather than a
34
+ * transform.
35
+ */
36
+ export const TURN_TIMEOUT_MS = 120_000;
37
+ /** Execution settings for one Teams conversation turn (an agent call). */
38
+ export const TEAMS_TURN_SETTINGS = {
39
+ default: {
40
+ // Cache disabled: a turn is conversational and stateful - the same text in
41
+ // a different conversation must not replay an earlier card.
42
+ cache: { enabled: false },
43
+ // Retry disabled: a turn may have run tools with side effects before it
44
+ // failed, and re-running the model would double them.
45
+ retry: { enabled: false },
46
+ timeout: TURN_TIMEOUT_MS,
47
+ },
48
+ };
49
+ /** Execution settings for posting a card to a Teams incoming webhook. */
50
+ export const TEAMS_POST_SETTINGS = {
51
+ default: {
52
+ // Cache disabled: a post is a side effect, not a value. Replaying a cached
53
+ // result would report a delivery that never happened.
54
+ cache: { enabled: false },
55
+ // Retry enabled: a webhook POST is idempotent enough that a transient 5xx
56
+ // or timeout at the Teams edge is worth one or two more attempts.
57
+ retry: { enabled: true, attempts: POST_ATTEMPTS },
58
+ timeout: POST_TIMEOUT_MS,
59
+ },
60
+ };
61
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVmYXVsdHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZGVmYXVsdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7R0FVRztBQXdCSCx3REFBd0Q7QUFDeEQsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsS0FBSyxDQUFDO0FBRXRDLDBEQUEwRDtBQUMxRCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsTUFBTSxDQUFDO0FBRXRDLGdFQUFnRTtBQUNoRSxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUcsQ0FBQyxDQUFDO0FBRS9CLDZFQUE2RTtBQUM3RSxNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBMkI7SUFDMUQsT0FBTyxFQUFFO1FBQ1AseUVBQXlFO1FBQ3pFLDZEQUE2RDtRQUM3RCxLQUFLLEVBQUUsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFO1FBQ3pCLGlFQUFpRTtRQUNqRSw2REFBNkQ7UUFDN0QsS0FBSyxFQUFFLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRTtRQUN6QixPQUFPLEVBQUUsZ0JBQWdCO0tBQzFCO0NBQ0YsQ0FBQztBQUVGOzs7OztHQUtHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLE9BQU8sQ0FBQztBQUV2QywwRUFBMEU7QUFDMUUsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQTJCO0lBQ3pELE9BQU8sRUFBRTtRQUNQLDJFQUEyRTtRQUMzRSw0REFBNEQ7UUFDNUQsS0FBSyxFQUFFLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRTtRQUN6Qix3RUFBd0U7UUFDeEUsc0RBQXNEO1FBQ3RELEtBQUssRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUU7UUFDekIsT0FBTyxFQUFFLGVBQWU7S0FDekI7Q0FDRixDQUFDO0FBRUYseUVBQXlFO0FBQ3pFLE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUEyQjtJQUN6RCxPQUFPLEVBQUU7UUFDUCwyRUFBMkU7UUFDM0Usc0RBQXNEO1FBQ3RELEtBQUssRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUU7UUFDekIsMEVBQTBFO1FBQzFFLGtFQUFrRTtRQUNsRSxLQUFLLEVBQUUsRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUU7UUFDakQsT0FBTyxFQUFFLGVBQWU7S0FDekI7Q0FDRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBJbnRlcmNlcHRvciBkZWZhdWx0cyBmb3IgdGhlIFRlYW1zIHBsdWdpbi5cbiAqXG4gKiBCdWlsZGluZyBhIGNhcmQgaXMgYSBwdXJlIHRyYW5zZm9ybSwgYnV0IGl0IHN0aWxsIHJ1bnMgdGhyb3VnaFxuICogYFBsdWdpbi5leGVjdXRlKClgIHNvIGl0IHNoYXJlcyB0aGUgYXBwJ3MgdGVsZW1ldHJ5IC8gdGltZW91dCBwb3N0dXJlOyB0aGVcbiAqIG9uZSBvcGVyYXRpb24gdGhhdCBkb2VzIEkvTyBpcyBwb3N0aW5nIGEgY2FyZCB0byBhIFRlYW1zIGluY29taW5nIHdlYmhvb2suXG4gKiBUaGUgc2V0dGluZ3MgYXJlIGtlcHQgaGVyZSByYXRoZXIgdGhhbiBhdCB0aGUgY2FsbCBzaXRlcyBzbyB0aGUgY2FjaGluZyAvXG4gKiByZXRyeSAvIHRpbWVvdXQgcG9zdHVyZSBvZiBlYWNoIGlzIHJldmlld2FibGUgaW4gb25lIHBsYWNlLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG4vKipcbiAqIFRoZSBgUGx1Z2luRXhlY3V0ZUNvbmZpZ2Agc2xpY2UgdGhpcyBwYWNrYWdlIHNldHMuIE1pcnJvcmVkIHN0cnVjdHVyYWxseVxuICogYmVjYXVzZSBBcHBLaXQncyBgUGx1Z2luRXhlY3V0ZUNvbmZpZ2AgbGl2ZXMgYmVoaW5kIGEgc3VicGF0aCBpdHMgYGV4cG9ydHNgXG4gKiBtYXAgZG9lcyBub3QgcHVibGlzaCwgc28gdGhlIG5vbWluYWwgdHlwZSBjYW5ub3QgYmUgaW1wb3J0ZWQuIFdyaXR0ZW4gYXMgYVxuICogdHlwZSBhbGlhcyByYXRoZXIgdGhhbiBhbiBpbnRlcmZhY2Ugc28gaXQgc3RheXMgYXNzaWduYWJsZSB0byB0aGUgbm9taW5hbFxuICogdHlwZSdzIGluZGV4IHNpZ25hdHVyZS5cbiAqL1xuZXhwb3J0IHR5cGUgVGVhbXNFeGVjdXRlQ29uZmlnID0ge1xuICBjYWNoZT86IHsgZW5hYmxlZD86IGJvb2xlYW47IHR0bD86IG51bWJlcjsgY2FjaGVLZXk/OiAoc3RyaW5nIHwgbnVtYmVyIHwgb2JqZWN0KVtdIH07XG4gIHJldHJ5PzogeyBlbmFibGVkPzogYm9vbGVhbjsgYXR0ZW1wdHM/OiBudW1iZXI7IGluaXRpYWxEZWxheT86IG51bWJlcjsgbWF4RGVsYXk/OiBudW1iZXIgfTtcbiAgdGltZW91dD86IG51bWJlcjtcbn07XG5cbi8qKlxuICogVGhlIGBQbHVnaW5FeGVjdXRpb25TZXR0aW5nc2Agc2hhcGUgYWNjZXB0ZWQgYnkgQXBwS2l0J3MgYFBsdWdpbi5leGVjdXRlKClgLlxuICogTWlycm9yZWQgc3RydWN0dXJhbGx5IGZvciB0aGUgc2FtZSByZWFzb24gYXMge0BsaW5rIFRlYW1zRXhlY3V0ZUNvbmZpZ30uXG4gKi9cbmV4cG9ydCB0eXBlIFRlYW1zRXhlY3V0aW9uU2V0dGluZ3MgPSB7XG4gIGRlZmF1bHQ6IFRlYW1zRXhlY3V0ZUNvbmZpZztcbiAgdXNlcj86IFRlYW1zRXhlY3V0ZUNvbmZpZztcbn07XG5cbi8qKiBDZWlsaW5nIG9uIGhvdyBsb25nIGEgc2luZ2xlIGNhcmQgYnVpbGQgbWF5IHRha2UuICovXG5leHBvcnQgY29uc3QgQlVJTERfVElNRU9VVF9NUyA9IDVfMDAwO1xuXG4vKiogQ2VpbGluZyBvbiBob3cgbG9uZyBhIHNpbmdsZSB3ZWJob29rIFBPU1QgbWF5IHRha2UuICovXG5leHBvcnQgY29uc3QgUE9TVF9USU1FT1VUX01TID0gMTVfMDAwO1xuXG4vKiogQXR0ZW1wdHMgYWxsb3dlZCBmb3IgYSB3ZWJob29rIFBPU1QsIGluY2x1ZGluZyB0aGUgZmlyc3QuICovXG5leHBvcnQgY29uc3QgUE9TVF9BVFRFTVBUUyA9IDM7XG5cbi8qKiBFeGVjdXRpb24gc2V0dGluZ3MgZm9yIGJ1aWxkaW5nIGEgY2FyZCAoYSBwdXJlLCBpbi1wcm9jZXNzIHRyYW5zZm9ybSkuICovXG5leHBvcnQgY29uc3QgVEVBTVNfQlVJTERfU0VUVElOR1M6IFRlYW1zRXhlY3V0aW9uU2V0dGluZ3MgPSB7XG4gIGRlZmF1bHQ6IHtcbiAgICAvLyBDYWNoZSBkaXNhYmxlZDogdGhlIGJ1aWxkIGlzIGNoZWFwIGFuZCBkZXRlcm1pbmlzdGljLCBzbyBhIGNhY2hlIHdvdWxkXG4gICAgLy8gYWRkIGEgY3Jvc3MtaWRlbnRpdHkga2V5IHN1cmZhY2UgZm9yIG5vIG1lYXN1cmFibGUgc2F2aW5nLlxuICAgIGNhY2hlOiB7IGVuYWJsZWQ6IGZhbHNlIH0sXG4gICAgLy8gUmV0cnkgZGlzYWJsZWQ6IHRoZSB0cmFuc2Zvcm0gcGVyZm9ybXMgbm8gSS9PLCBzbyBhIGZhaWx1cmUgaXNcbiAgICAvLyBkZXRlcm1pbmlzdGljIGFuZCBhIHNlY29uZCBhdHRlbXB0IHdvdWxkIGZhaWwgaWRlbnRpY2FsbHkuXG4gICAgcmV0cnk6IHsgZW5hYmxlZDogZmFsc2UgfSxcbiAgICB0aW1lb3V0OiBCVUlMRF9USU1FT1VUX01TLFxuICB9LFxufTtcblxuLyoqXG4gKiBDZWlsaW5nIG9uIG9uZSBjb252ZXJzYXRpb24gdHVybi4gR2VuZXJvdXMgcmVsYXRpdmUgdG8gdGhlIG90aGVyIHR3bzogYSB0dXJuXG4gKiBzcGFucyBhIGZ1bGwgYWdlbnQgY2FsbCAobW9kZWwgbGF0ZW5jeSBwbHVzIGFueSB0b29sIHRoZSBhZ2VudCBydW5zKSwgc28gaXQgaXNcbiAqIGJvdW5kZWQgYnkgdGhlIHNhbWUgb3JkZXIgb2YgbWFnbml0dWRlIGFzIGEgY2hhdCByZXF1ZXN0IHJhdGhlciB0aGFuIGFcbiAqIHRyYW5zZm9ybS5cbiAqL1xuZXhwb3J0IGNvbnN0IFRVUk5fVElNRU9VVF9NUyA9IDEyMF8wMDA7XG5cbi8qKiBFeGVjdXRpb24gc2V0dGluZ3MgZm9yIG9uZSBUZWFtcyBjb252ZXJzYXRpb24gdHVybiAoYW4gYWdlbnQgY2FsbCkuICovXG5leHBvcnQgY29uc3QgVEVBTVNfVFVSTl9TRVRUSU5HUzogVGVhbXNFeGVjdXRpb25TZXR0aW5ncyA9IHtcbiAgZGVmYXVsdDoge1xuICAgIC8vIENhY2hlIGRpc2FibGVkOiBhIHR1cm4gaXMgY29udmVyc2F0aW9uYWwgYW5kIHN0YXRlZnVsIC0gdGhlIHNhbWUgdGV4dCBpblxuICAgIC8vIGEgZGlmZmVyZW50IGNvbnZlcnNhdGlvbiBtdXN0IG5vdCByZXBsYXkgYW4gZWFybGllciBjYXJkLlxuICAgIGNhY2hlOiB7IGVuYWJsZWQ6IGZhbHNlIH0sXG4gICAgLy8gUmV0cnkgZGlzYWJsZWQ6IGEgdHVybiBtYXkgaGF2ZSBydW4gdG9vbHMgd2l0aCBzaWRlIGVmZmVjdHMgYmVmb3JlIGl0XG4gICAgLy8gZmFpbGVkLCBhbmQgcmUtcnVubmluZyB0aGUgbW9kZWwgd291bGQgZG91YmxlIHRoZW0uXG4gICAgcmV0cnk6IHsgZW5hYmxlZDogZmFsc2UgfSxcbiAgICB0aW1lb3V0OiBUVVJOX1RJTUVPVVRfTVMsXG4gIH0sXG59O1xuXG4vKiogRXhlY3V0aW9uIHNldHRpbmdzIGZvciBwb3N0aW5nIGEgY2FyZCB0byBhIFRlYW1zIGluY29taW5nIHdlYmhvb2suICovXG5leHBvcnQgY29uc3QgVEVBTVNfUE9TVF9TRVRUSU5HUzogVGVhbXNFeGVjdXRpb25TZXR0aW5ncyA9IHtcbiAgZGVmYXVsdDoge1xuICAgIC8vIENhY2hlIGRpc2FibGVkOiBhIHBvc3QgaXMgYSBzaWRlIGVmZmVjdCwgbm90IGEgdmFsdWUuIFJlcGxheWluZyBhIGNhY2hlZFxuICAgIC8vIHJlc3VsdCB3b3VsZCByZXBvcnQgYSBkZWxpdmVyeSB0aGF0IG5ldmVyIGhhcHBlbmVkLlxuICAgIGNhY2hlOiB7IGVuYWJsZWQ6IGZhbHNlIH0sXG4gICAgLy8gUmV0cnkgZW5hYmxlZDogYSB3ZWJob29rIFBPU1QgaXMgaWRlbXBvdGVudCBlbm91Z2ggdGhhdCBhIHRyYW5zaWVudCA1eHhcbiAgICAvLyBvciB0aW1lb3V0IGF0IHRoZSBUZWFtcyBlZGdlIGlzIHdvcnRoIG9uZSBvciB0d28gbW9yZSBhdHRlbXB0cy5cbiAgICByZXRyeTogeyBlbmFibGVkOiB0cnVlLCBhdHRlbXB0czogUE9TVF9BVFRFTVBUUyB9LFxuICAgIHRpbWVvdXQ6IFBPU1RfVElNRU9VVF9NUyxcbiAgfSxcbn07XG4iXX0=
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The Teams messaging endpoint's turn: what happens between Azure Bot Service
3
+ * POSTing an activity and a card appearing in the channel.
4
+ *
5
+ * Split out from `conversation.ts` because the two callers have genuinely
6
+ * different contracts. `conversation.ts` runs a turn and RETURNS the reply, which
7
+ * is what a direct HTTP caller (or the in-repo preview UI) wants. A real channel
8
+ * cannot work that way: Bot Service ignores the response body and expects a fast
9
+ * `200`, so this module acknowledges first and delivers the reply out-of-band
10
+ * through the Connector API.
11
+ *
12
+ * The sequence for a `message` activity:
13
+ *
14
+ * 1. validate the inbound JWT and pin the reply destination to the
15
+ * token's `serviceUrl` (see `auth.ts`);
16
+ * 2. return `200` immediately - Bot Service retries an activity it thinks
17
+ * timed out, and a duplicate turn means a duplicate card;
18
+ * 3. show the typing indicator, run the agent, and POST the card back.
19
+ *
20
+ * Step 2 is why this is fire-and-forget rather than awaited: a card-producing
21
+ * agent turn takes seconds to tens of seconds, far longer than the ~15s Bot
22
+ * Service allows a bot to acknowledge.
23
+ *
24
+ * @module
25
+ */
26
+ import { activity as activityContract } from "@dbx-tools/shared-teams";
27
+ import { type CardAgentLike, type CardContextFactory } from "./conversation.js";
28
+ /** Resolved bot credentials a delivered turn needs. */
29
+ export interface BotCredentials {
30
+ /** Entra app (client) id of the bot registration. */
31
+ appId: string;
32
+ /** Client secret for {@link appId}. */
33
+ appPassword: string;
34
+ /** Tenant id for a single-tenant bot. */
35
+ appTenantId?: string;
36
+ }
37
+ /** Everything {@link deliverTurn} needs to answer one inbound activity. */
38
+ export interface DeliverTurnOptions {
39
+ /** The agent that composes the card. */
40
+ agent: CardAgentLike;
41
+ /** The validated inbound activity. */
42
+ activity: activityContract.Activity;
43
+ /** Bot credentials used to fetch the outbound Connector token. */
44
+ credentials: BotCredentials;
45
+ /**
46
+ * Reply destination, already validated against the inbound token. Passing this
47
+ * explicitly (rather than reading `activity.serviceUrl`) keeps the security
48
+ * decision in the caller, where the token is in scope.
49
+ */
50
+ serviceUrl: string;
51
+ /**
52
+ * Builds the agent's per-turn request context, so the delivered turn has the
53
+ * same tool reach (Genie and every other user-scoped tool) as a chat turn.
54
+ */
55
+ createRequestContext?: CardContextFactory;
56
+ /** Cancels the turn (process shutdown, or a test tearing down). */
57
+ signal?: AbortSignal;
58
+ }
59
+ /**
60
+ * Read the reply destination off an inbound activity, if it names a usable one.
61
+ *
62
+ * `serviceUrl` rides on the activity as a plain string, so it is validated
63
+ * against the verified token's own `serviceurl` claim before anything
64
+ * authenticated is sent there.
65
+ */
66
+ export declare const resolveServiceUrl: (activity: activityContract.Activity, tokenServiceUrl?: string) => string | null;
67
+ /**
68
+ * Run one turn and deliver the card back through the Connector API.
69
+ *
70
+ * Awaited by nobody on the request path (the route has already answered `200`),
71
+ * so this owns its own error handling: a failure posts {@link FAILURE_TEXT} to
72
+ * the channel and is logged, never rethrown into an unhandled rejection.
73
+ */
74
+ export declare const deliverTurn: (options: DeliverTurnOptions) => Promise<void>;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The Teams messaging endpoint's turn: what happens between Azure Bot Service
3
+ * POSTing an activity and a card appearing in the channel.
4
+ *
5
+ * Split out from `conversation.ts` because the two callers have genuinely
6
+ * different contracts. `conversation.ts` runs a turn and RETURNS the reply, which
7
+ * is what a direct HTTP caller (or the in-repo preview UI) wants. A real channel
8
+ * cannot work that way: Bot Service ignores the response body and expects a fast
9
+ * `200`, so this module acknowledges first and delivers the reply out-of-band
10
+ * through the Connector API.
11
+ *
12
+ * The sequence for a `message` activity:
13
+ *
14
+ * 1. validate the inbound JWT and pin the reply destination to the
15
+ * token's `serviceUrl` (see `auth.ts`);
16
+ * 2. return `200` immediately - Bot Service retries an activity it thinks
17
+ * timed out, and a duplicate turn means a duplicate card;
18
+ * 3. show the typing indicator, run the agent, and POST the card back.
19
+ *
20
+ * Step 2 is why this is fire-and-forget rather than awaited: a card-producing
21
+ * agent turn takes seconds to tens of seconds, far longer than the ~15s Bot
22
+ * Service allows a bot to acknowledge.
23
+ *
24
+ * @module
25
+ */
26
+ import { error, log } from "@dbx-tools/shared-core";
27
+ import { connectorToken, isAllowedServiceUrl } from "./auth.js";
28
+ import { sendActivity, sendTyping } from "./connector.js";
29
+ import { runCardTurn } from "./conversation.js";
30
+ const logger = log.logger("teams:messaging");
31
+ /**
32
+ * Message shown in the channel when a turn fails.
33
+ *
34
+ * A bot that silently drops a failed turn looks broken - the user sees their
35
+ * message land and nothing come back, forever. A short apology is posted instead
36
+ * so the conversation stays legible; the real error goes to the logs.
37
+ */
38
+ const FAILURE_TEXT = "Sorry - I could not put together an answer for that. Please try again.";
39
+ /**
40
+ * Read the reply destination off an inbound activity, if it names a usable one.
41
+ *
42
+ * `serviceUrl` rides on the activity as a plain string, so it is validated
43
+ * against the verified token's own `serviceurl` claim before anything
44
+ * authenticated is sent there.
45
+ */
46
+ export const resolveServiceUrl = (activity, tokenServiceUrl) => {
47
+ const raw = activity.serviceUrl;
48
+ const candidate = typeof raw === "string" ? raw.trim() : "";
49
+ if (!candidate)
50
+ return null;
51
+ return isAllowedServiceUrl(candidate, tokenServiceUrl) ? candidate : null;
52
+ };
53
+ /**
54
+ * Run one turn and deliver the card back through the Connector API.
55
+ *
56
+ * Awaited by nobody on the request path (the route has already answered `200`),
57
+ * so this owns its own error handling: a failure posts {@link FAILURE_TEXT} to
58
+ * the channel and is logged, never rethrown into an unhandled rejection.
59
+ */
60
+ export const deliverTurn = async (options) => {
61
+ const { agent, activity, credentials, serviceUrl } = options;
62
+ const conversationId = activity.conversation?.id;
63
+ if (!conversationId) {
64
+ logger.warn("dropping activity with no conversation id");
65
+ return;
66
+ }
67
+ const base = {
68
+ serviceUrl,
69
+ conversationId,
70
+ ...(activity.id ? { replyToId: activity.id } : {}),
71
+ ...(options.signal ? { signal: options.signal } : {}),
72
+ };
73
+ let token;
74
+ try {
75
+ token = await connectorToken({
76
+ appId: credentials.appId,
77
+ appPassword: credentials.appPassword,
78
+ ...(credentials.appTenantId ? { appTenantId: credentials.appTenantId } : {}),
79
+ ...(options.signal ? { signal: options.signal } : {}),
80
+ });
81
+ }
82
+ catch (err) {
83
+ // No token means nothing can be delivered - not even the apology - so this
84
+ // is the one failure that can only be logged.
85
+ logger.error("could not obtain a connector token", { error: error.errorMessage(err) });
86
+ return;
87
+ }
88
+ const target = { ...base, token };
89
+ await sendTyping(target);
90
+ try {
91
+ const activities = await runCardTurn(agent, activity, {
92
+ ...(options.createRequestContext
93
+ ? { createRequestContext: options.createRequestContext }
94
+ : {}),
95
+ ...(options.signal ? { signal: options.signal } : {}),
96
+ });
97
+ for (const reply of activities) {
98
+ await sendActivity(reply, target);
99
+ }
100
+ logger.info("turn delivered", { conversation: conversationId, replies: activities.length });
101
+ }
102
+ catch (err) {
103
+ logger.error("turn failed", { conversation: conversationId, error: error.errorMessage(err) });
104
+ try {
105
+ await sendActivity({ type: "message", text: FAILURE_TEXT }, target);
106
+ }
107
+ catch (postErr) {
108
+ logger.error("could not report the failure to the channel", {
109
+ error: error.errorMessage(postErr),
110
+ });
111
+ }
112
+ }
113
+ };
114
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVzc2FnaW5nLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL21lc3NhZ2luZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBd0JHO0FBRUgsT0FBTyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUVwRCxPQUFPLEVBQUUsY0FBYyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQzdELE9BQU8sRUFBRSxZQUFZLEVBQUUsVUFBVSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3ZELE9BQU8sRUFBRSxXQUFXLEVBQStDLE1BQU0sZ0JBQWdCLENBQUM7QUFFMUYsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0FBRTdDOzs7Ozs7R0FNRztBQUNILE1BQU0sWUFBWSxHQUFHLHdFQUF3RSxDQUFDO0FBbUM5Rjs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxDQUMvQixRQUFtQyxFQUNuQyxlQUF3QixFQUNULEVBQUU7SUFDakIsTUFBTSxHQUFHLEdBQUksUUFBcUMsQ0FBQyxVQUFVLENBQUM7SUFDOUQsTUFBTSxTQUFTLEdBQUcsT0FBTyxHQUFHLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUM1RCxJQUFJLENBQUMsU0FBUztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQzVCLE9BQU8sbUJBQW1CLENBQUMsU0FBUyxFQUFFLGVBQWUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztBQUM1RSxDQUFDLENBQUM7QUFFRjs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsS0FBSyxFQUFFLE9BQTJCLEVBQWlCLEVBQUU7SUFDOUUs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QUksRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLFlBQVksRUFBRSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3RFLENBQUM7UUFBQyxPQUFPLE9BQU8sRUFBRSxDQUFDO1lBQ2pCLE1BQU0sQ0FBQyxLQUFLLENBQUMsNkNBQTZDLEVBQUU7Z0JBQzFELEtBQUssRUFBRSxLQUFLLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQzthQUNuQyxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztBQUNILENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVGhlIFRlYW1zIG1lc3NhZ2luZyBlbmRwb2ludCdzIHR1cm46IHdoYXQgaGFwcGVucyBiZXR3ZWVuIEF6dXJlIEJvdCBTZXJ2aWNlXG4gKiBQT1NUaW5nIGFuIGFjdGl2aXR5IGFuZCBhIGNhcmQgYXBwZWFyaW5nIGluIHRoZSBjaGFubmVsLlxuICpcbiAqIFNwbGl0IG91dCBmcm9tIGBjb252ZXJzYXRpb24udHNgIGJlY2F1c2UgdGhlIHR3byBjYWxsZXJzIGhhdmUgZ2VudWluZWx5XG4gKiBkaWZmZXJlbnQgY29udHJhY3RzLiBgY29udmVyc2F0aW9uLnRzYCBydW5zIGEgdHVybiBhbmQgUkVUVVJOUyB0aGUgcmVwbHksIHdoaWNoXG4gKiBpcyB3aGF0IGEgZGlyZWN0IEhUVFAgY2FsbGVyIChvciB0aGUgaW4tcmVwbyBwcmV2aWV3IFVJKSB3YW50cy4gQSByZWFsIGNoYW5uZWxcbiAqIGNhbm5vdCB3b3JrIHRoYXQgd2F5OiBCb3QgU2VydmljZSBpZ25vcmVzIHRoZSByZXNwb25zZSBib2R5IGFuZCBleHBlY3RzIGEgZmFzdFxuICogYDIwMGAsIHNvIHRoaXMgbW9kdWxlIGFja25vd2xlZGdlcyBmaXJzdCBhbmQgZGVsaXZlcnMgdGhlIHJlcGx5IG91dC1vZi1iYW5kXG4gKiB0aHJvdWdoIHRoZSBDb25uZWN0b3IgQVBJLlxuICpcbiAqIFRoZSBzZXF1ZW5jZSBmb3IgYSBgbWVzc2FnZWAgYWN0aXZpdHk6XG4gKlxuICogICAxLiB2YWxpZGF0ZSB0aGUgaW5ib3VuZCBKV1QgYW5kIHBpbiB0aGUgcmVwbHkgZGVzdGluYXRpb24gdG8gdGhlXG4gKiAgICAgIHRva2VuJ3MgYHNlcnZpY2VVcmxgIChzZWUgYGF1dGgudHNgKTtcbiAqICAgMi4gcmV0dXJuIGAyMDBgIGltbWVkaWF0ZWx5IC0gQm90IFNlcnZpY2UgcmV0cmllcyBhbiBhY3Rpdml0eSBpdCB0aGlua3NcbiAqICAgICAgdGltZWQgb3V0LCBhbmQgYSBkdXBsaWNhdGUgdHVybiBtZWFucyBhIGR1cGxpY2F0ZSBjYXJkO1xuICogICAzLiBzaG93IHRoZSB0eXBpbmcgaW5kaWNhdG9yLCBydW4gdGhlIGFnZW50LCBhbmQgUE9TVCB0aGUgY2FyZCBiYWNrLlxuICpcbiAqIFN0ZXAgMiBpcyB3aHkgdGhpcyBpcyBmaXJlLWFuZC1mb3JnZXQgcmF0aGVyIHRoYW4gYXdhaXRlZDogYSBjYXJkLXByb2R1Y2luZ1xuICogYWdlbnQgdHVybiB0YWtlcyBzZWNvbmRzIHRvIHRlbnMgb2Ygc2Vjb25kcywgZmFyIGxvbmdlciB0aGFuIHRoZSB+MTVzIEJvdFxuICogU2VydmljZSBhbGxvd3MgYSBib3QgdG8gYWNrbm93bGVkZ2UuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IGVycm9yLCBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgYWN0aXZpdHkgYXMgYWN0aXZpdHlDb250cmFjdCB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC10ZWFtc1wiO1xuaW1wb3J0IHsgY29ubmVjdG9yVG9rZW4sIGlzQWxsb3dlZFNlcnZpY2VVcmwgfSBmcm9tIFwiLi9hdXRoXCI7XG5pbXBvcnQgeyBzZW5kQWN0aXZpdHksIHNlbmRUeXBpbmcgfSBmcm9tIFwiLi9jb25uZWN0b3JcIjtcbmltcG9ydCB7IHJ1bkNhcmRUdXJuLCB0eXBlIENhcmRBZ2VudExpa2UsIHR5cGUgQ2FyZENvbnRleHRGYWN0b3J5IH0gZnJvbSBcIi4vY29udmVyc2F0aW9uXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJ0ZWFtczptZXNzYWdpbmdcIik7XG5cbi8qKlxuICogTWVzc2FnZSBzaG93biBpbiB0aGUgY2hhbm5lbCB3aGVuIGEgdHVybiBmYWlscy5cbiAqXG4gKiBBIGJvdCB0aGF0IHNpbGVudGx5IGRyb3BzIGEgZmFpbGVkIHR1cm4gbG9va3MgYnJva2VuIC0gdGhlIHVzZXIgc2VlcyB0aGVpclxuICogbWVzc2FnZSBsYW5kIGFuZCBub3RoaW5nIGNvbWUgYmFjaywgZm9yZXZlci4gQSBzaG9ydCBhcG9sb2d5IGlzIHBvc3RlZCBpbnN0ZWFkXG4gKiBzbyB0aGUgY29udmVyc2F0aW9uIHN0YXlzIGxlZ2libGU7IHRoZSByZWFsIGVycm9yIGdvZXMgdG8gdGhlIGxvZ3MuXG4gKi9cbmNvbnN0IEZBSUxVUkVfVEVYVCA9IFwiU29ycnkgLSBJIGNvdWxkIG5vdCBwdXQgdG9nZXRoZXIgYW4gYW5zd2VyIGZvciB0aGF0LiBQbGVhc2UgdHJ5IGFnYWluLlwiO1xuXG4vKiogUmVzb2x2ZWQgYm90IGNyZWRlbnRpYWxzIGEgZGVsaXZlcmVkIHR1cm4gbmVlZHMuICovXG5leHBvcnQgaW50ZXJmYWNlIEJvdENyZWRlbnRpYWxzIHtcbiAgLyoqIEVudHJhIGFwcCAoY2xpZW50KSBpZCBvZiB0aGUgYm90IHJlZ2lzdHJhdGlvbi4gKi9cbiAgYXBwSWQ6IHN0cmluZztcbiAgLyoqIENsaWVudCBzZWNyZXQgZm9yIHtAbGluayBhcHBJZH0uICovXG4gIGFwcFBhc3N3b3JkOiBzdHJpbmc7XG4gIC8qKiBUZW5hbnQgaWQgZm9yIGEgc2luZ2xlLXRlbmFudCBib3QuICovXG4gIGFwcFRlbmFudElkPzogc3RyaW5nO1xufVxuXG4vKiogRXZlcnl0aGluZyB7QGxpbmsgZGVsaXZlclR1cm59IG5lZWRzIHRvIGFuc3dlciBvbmUgaW5ib3VuZCBhY3Rpdml0eS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRGVsaXZlclR1cm5PcHRpb25zIHtcbiAgLyoqIFRoZSBhZ2VudCB0aGF0IGNvbXBvc2VzIHRoZSBjYXJkLiAqL1xuICBhZ2VudDogQ2FyZEFnZW50TGlrZTtcbiAgLyoqIFRoZSB2YWxpZGF0ZWQgaW5ib3VuZCBhY3Rpdml0eS4gKi9cbiAgYWN0aXZpdHk6IGFjdGl2aXR5Q29udHJhY3QuQWN0aXZpdHk7XG4gIC8qKiBCb3QgY3JlZGVudGlhbHMgdXNlZCB0byBmZXRjaCB0aGUgb3V0Ym91bmQgQ29ubmVjdG9yIHRva2VuLiAqL1xuICBjcmVkZW50aWFsczogQm90Q3JlZGVudGlhbHM7XG4gIC8qKlxuICAgKiBSZXBseSBkZXN0aW5hdGlvbiwgYWxyZWFkeSB2YWxpZGF0ZWQgYWdhaW5zdCB0aGUgaW5ib3VuZCB0b2tlbi4gUGFzc2luZyB0aGlzXG4gICAqIGV4cGxpY2l0bHkgKHJhdGhlciB0aGFuIHJlYWRpbmcgYGFjdGl2aXR5LnNlcnZpY2VVcmxgKSBrZWVwcyB0aGUgc2VjdXJpdHlcbiAgICogZGVjaXNpb24gaW4gdGhlIGNhbGxlciwgd2hlcmUgdGhlIHRva2VuIGlzIGluIHNjb3BlLlxuICAgKi9cbiAgc2VydmljZVVybDogc3RyaW5nO1xuICAvKipcbiAgICogQnVpbGRzIHRoZSBhZ2VudCdzIHBlci10dXJuIHJlcXVlc3QgY29udGV4dCwgc28gdGhlIGRlbGl2ZXJlZCB0dXJuIGhhcyB0aGVcbiAgICogc2FtZSB0b29sIHJlYWNoIChHZW5pZSBhbmQgZXZlcnkgb3RoZXIgdXNlci1zY29wZWQgdG9vbCkgYXMgYSBjaGF0IHR1cm4uXG4gICAqL1xuICBjcmVhdGVSZXF1ZXN0Q29udGV4dD86IENhcmRDb250ZXh0RmFjdG9yeTtcbiAgLyoqIENhbmNlbHMgdGhlIHR1cm4gKHByb2Nlc3Mgc2h1dGRvd24sIG9yIGEgdGVzdCB0ZWFyaW5nIGRvd24pLiAqL1xuICBzaWduYWw/OiBBYm9ydFNpZ25hbDtcbn1cblxuLyoqXG4gKiBSZWFkIHRoZSByZXBseSBkZXN0aW5hdGlvbiBvZmYgYW4gaW5ib3VuZCBhY3Rpdml0eSwgaWYgaXQgbmFtZXMgYSB1c2FibGUgb25lLlxuICpcbiAqIGBzZXJ2aWNlVXJsYCByaWRlcyBvbiB0aGUgYWN0aXZpdHkgYXMgYSBwbGFpbiBzdHJpbmcsIHNvIGl0IGlzIHZhbGlkYXRlZFxuICogYWdhaW5zdCB0aGUgdmVyaWZpZWQgdG9rZW4ncyBvd24gYHNlcnZpY2V1cmxgIGNsYWltIGJlZm9yZSBhbnl0aGluZ1xuICogYXV0aGVudGljYXRlZCBpcyBzZW50IHRoZXJlLlxuICovXG5leHBvcnQgY29uc3QgcmVzb2x2ZVNlcnZpY2VVcmwgPSAoXG4gIGFjdGl2aXR5OiBhY3Rpdml0eUNvbnRyYWN0LkFjdGl2aXR5LFxuICB0b2tlblNlcnZpY2VVcmw/OiBzdHJpbmcsXG4pOiBzdHJpbmcgfCBudWxsID0+IHtcbiAgY29uc3QgcmF3ID0gKGFjdGl2aXR5IGFzIHsgc2VydmljZVVybD86IHVua25vd24gfSkuc2VydmljZVVybDtcbiAgY29uc3QgY2FuZGlkYXRlID0gdHlwZW9mIHJhdyA9PT0gXCJzdHJpbmdcIiA/IHJhdy50cmltKCkgOiBcIlwiO1xuICBpZiAoIWNhbmRpZGF0ZSkgcmV0dXJuIG51bGw7XG4gIHJldHVybiBpc0FsbG93ZWRTZXJ2aWNlVXJsKGNhbmRpZGF0ZSwgdG9rZW5TZXJ2aWNlVXJsKSA/IGNhbmRpZGF0ZSA6IG51bGw7XG59O1xuXG4vKipcbiAqIFJ1biBvbmUgdHVybiBhbmQgZGVsaXZlciB0aGUgY2FyZCBiYWNrIHRocm91Z2ggdGhlIENvbm5lY3RvciBBUEkuXG4gKlxuICogQXdhaXRlZCBieSBub2JvZHkgb24gdGhlIHJlcXVlc3QgcGF0aCAodGhlIHJvdXRlIGhhcyBhbHJlYWR5IGFuc3dlcmVkIGAyMDBgKSxcbiAqIHNvIHRoaXMgb3ducyBpdHMgb3duIGVycm9yIGhhbmRsaW5nOiBhIGZhaWx1cmUgcG9zdHMge0BsaW5rIEZBSUxVUkVfVEVYVH0gdG9cbiAqIHRoZSBjaGFubmVsIGFuZCBpcyBsb2dnZWQsIG5ldmVyIHJldGhyb3duIGludG8gYW4gdW5oYW5kbGVkIHJlamVjdGlvbi5cbiAqL1xuZXhwb3J0IGNvbnN0IGRlbGl2ZXJUdXJuID0gYXN5bmMgKG9wdGlvbnM6IERlbGl2ZXJUdXJuT3B0aW9ucyk6IFByb21pc2U8dm9pZD4gPT4ge1xuICBjb25zdCB7IGFnZW50LCBhY3Rpdml0eSwgY3JlZGVudGlhbHMsIHNlcnZpY2VVcmwgfSA9IG9wdGlvbnM7XG4gIGNvbnN0IGNvbnZlcnNhdGlvbklkID0gYWN0aXZpdHkuY29udmVyc2F0aW9uPy5pZDtcbiAgaWYgKCFjb252ZXJzYXRpb25JZCkge1xuICAgIGxvZ2dlci53YXJuKFwiZHJvcHBpbmcgYWN0aXZpdHkgd2l0aCBubyBjb252ZXJzYXRpb24gaWRcIik7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgY29uc3QgYmFzZSA9IHtcbiAgICBzZXJ2aWNlVXJsLFxuICAgIGNvbnZlcnNhdGlvbklkLFxuICAgIC4uLihhY3Rpdml0eS5pZCA/IHsgcmVwbHlUb0lkOiBhY3Rpdml0eS5pZCB9IDoge30pLFxuICAgIC4uLihvcHRpb25zLnNpZ25hbCA/IHsgc2lnbmFsOiBvcHRpb25zLnNpZ25hbCB9IDoge30pLFxuICB9O1xuXG4gIGxldCB0b2tlbjogc3RyaW5nO1xuICB0cnkge1xuICAgIHRva2VuID0gYXdhaXQgY29ubmVjdG9yVG9rZW4oe1xuICAgICAgYXBwSWQ6IGNyZWRlbnRpYWxzLmFwcElkLFxuICAgICAgYXBwUGFzc3dvcmQ6IGNyZWRlbnRpYWxzLmFwcFBhc3N3b3JkLFxuICAgICAgLi4uKGNyZWRlbnRpYWxzLmFwcFRlbmFudElkID8geyBhcHBUZW5hbnRJZDogY3JlZGVudGlhbHMuYXBwVGVuYW50SWQgfSA6IHt9KSxcbiAgICAgIC4uLihvcHRpb25zLnNpZ25hbCA/IHsgc2lnbmFsOiBvcHRpb25zLnNpZ25hbCB9IDoge30pLFxuICAgIH0pO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICAvLyBObyB0b2tlbiBtZWFucyBub3RoaW5nIGNhbiBiZSBkZWxpdmVyZWQgLSBub3QgZXZlbiB0aGUgYXBvbG9neSAtIHNvIHRoaXNcbiAgICAvLyBpcyB0aGUgb25lIGZhaWx1cmUgdGhhdCBjYW4gb25seSBiZSBsb2dnZWQuXG4gICAgbG9nZ2VyLmVycm9yKFwiY291bGQgbm90IG9idGFpbiBhIGNvbm5lY3RvciB0b2tlblwiLCB7IGVycm9yOiBlcnJvci5lcnJvck1lc3NhZ2UoZXJyKSB9KTtcbiAgICByZXR1cm47XG4gIH1cblxuICBjb25zdCB0YXJnZXQgPSB7IC4uLmJhc2UsIHRva2VuIH07XG4gIGF3YWl0IHNlbmRUeXBpbmcodGFyZ2V0KTtcblxuICB0cnkge1xuICAgIGNvbnN0IGFjdGl2aXRpZXMgPSBhd2FpdCBydW5DYXJkVHVybihhZ2VudCwgYWN0aXZpdHksIHtcbiAgICAgIC4uLihvcHRpb25zLmNyZWF0ZVJlcXVlc3RDb250ZXh0XG4gICAgICAgID8geyBjcmVhdGVSZXF1ZXN0Q29udGV4dDogb3B0aW9ucy5jcmVhdGVSZXF1ZXN0Q29udGV4dCB9XG4gICAgICAgIDoge30pLFxuICAgICAgLi4uKG9wdGlvbnMuc2lnbmFsID8geyBzaWduYWw6IG9wdGlvbnMuc2lnbmFsIH0gOiB7fSksXG4gICAgfSk7XG4gICAgZm9yIChjb25zdCByZXBseSBvZiBhY3Rpdml0aWVzKSB7XG4gICAgICBhd2FpdCBzZW5kQWN0aXZpdHkocmVwbHksIHRhcmdldCk7XG4gICAgfVxuICAgIGxvZ2dlci5pbmZvKFwidHVybiBkZWxpdmVyZWRcIiwgeyBjb252ZXJzYXRpb246IGNvbnZlcnNhdGlvbklkLCByZXBsaWVzOiBhY3Rpdml0aWVzLmxlbmd0aCB9KTtcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgbG9nZ2VyLmVycm9yKFwidHVybiBmYWlsZWRcIiwgeyBjb252ZXJzYXRpb246IGNvbnZlcnNhdGlvbklkLCBlcnJvcjogZXJyb3IuZXJyb3JNZXNzYWdlKGVycikgfSk7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHNlbmRBY3Rpdml0eSh7IHR5cGU6IFwibWVzc2FnZVwiLCB0ZXh0OiBGQUlMVVJFX1RFWFQgfSwgdGFyZ2V0KTtcbiAgICB9IGNhdGNoIChwb3N0RXJyKSB7XG4gICAgICBsb2dnZXIuZXJyb3IoXCJjb3VsZCBub3QgcmVwb3J0IHRoZSBmYWlsdXJlIHRvIHRoZSBjaGFubmVsXCIsIHtcbiAgICAgICAgZXJyb3I6IGVycm9yLmVycm9yTWVzc2FnZShwb3N0RXJyKSxcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxufTtcbiJdfQ==
@@ -0,0 +1,185 @@
1
+ /**
2
+ * AppKit plugin (registered name: `teams`) that owns the Teams Adaptive Card
3
+ * runtime - the resolved card version and the optional incoming-webhook URL the
4
+ * {@link teamsCardTool} and the AppKit `teams.createCard` tool read. Registering
5
+ * it resolves and logs the effective config (which card version is in force,
6
+ * whether a webhook is wired up) so a misconfiguration is visible in the boot
7
+ * logs rather than on the first card, and installs the plugin's `execute()` as
8
+ * the runtime's executor so every build / post picks up AppKit's cache / retry /
9
+ * timeout / telemetry chain.
10
+ *
11
+ * The plugin is also a `ToolProvider`, so an AppKit agent can reach a
12
+ * `teams.createCard` tool directly; the {@link teamsCardTool} export is the same
13
+ * capability for a Mastra agent. Both share the runtime primed here.
14
+ *
15
+ * The plugin mounts four routes under its base path (`/api/teams`):
16
+ *
17
+ * - `POST /messages` is the REAL Microsoft Teams messaging endpoint - the URL
18
+ * an Azure Bot registration points at. It validates the Bot Service JWT,
19
+ * acknowledges immediately, and delivers the agent's card back over the
20
+ * Connector API. This is the route that makes a Teams channel able to chat
21
+ * with the app's agents, the same way the Mastra plugin exposes MCP at a
22
+ * path.
23
+ * - `POST /activity` runs the same turn SYNCHRONOUSLY, answering with the
24
+ * reply activities in the response body. It needs no bot registration, so
25
+ * it is what a local client (the in-repo preview chat), a test, or any
26
+ * non-Teams caller uses.
27
+ * - `POST /card` compiles a {@link card.CardSpec} into an Adaptive Card
28
+ * document (the preview page posts here).
29
+ * - `POST /post` pushes a compiled card to the configured Teams incoming
30
+ * webhook when one is set.
31
+ *
32
+ * Mirrors the node-email add-on's shape.
33
+ *
34
+ * @module
35
+ */
36
+ import { Plugin, type IAppRouter } from "@databricks/appkit";
37
+ import { type AgentToolDefinition, type ToolProvider } from "@databricks/appkit/beta";
38
+ import { card } from "@dbx-tools/shared-teams";
39
+ import { type TeamsPluginConfig } from "./config.js";
40
+ /**
41
+ * AppKit plugin that configures the Adaptive Card builder used by the
42
+ * `create_teams_card` tool, and exposes card building as an AppKit agent tool.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import { createApp, server } from "@databricks/appkit";
47
+ * import { plugin as teamsPlugin } from "@dbx-tools/teams";
48
+ *
49
+ * await createApp({
50
+ * plugins: [
51
+ * server(),
52
+ * teamsPlugin.teams({ webhookUrl: process.env.TEAMS_WEBHOOK_URL }),
53
+ * ],
54
+ * });
55
+ * ```
56
+ */
57
+ export declare class TeamsPlugin extends Plugin<TeamsPluginConfig> implements ToolProvider {
58
+ static manifest: {
59
+ name: "teams";
60
+ displayName: string;
61
+ description: string;
62
+ stability: "beta";
63
+ resources: {
64
+ required: never[];
65
+ optional: never[];
66
+ };
67
+ config: {
68
+ schema: import("json-schema").JSONSchema7;
69
+ };
70
+ };
71
+ /**
72
+ * The tool this plugin offers to an AppKit agent.
73
+ *
74
+ * Marked `autoInheritable`: building a card is a pure, side-effect-free
75
+ * transform (nothing leaves the building), so it is safe to hand to any
76
+ * agent by default - unlike a send.
77
+ *
78
+ * `execute` re-parses its arguments with the local schema: AppKit validates
79
+ * against the same schema first, but re-parsing is what gives the body typed
80
+ * arguments instead of `unknown`.
81
+ */
82
+ private readonly tools;
83
+ /**
84
+ * Prime the shared runtime from this plugin's config (over env), route the
85
+ * tool's builds through this plugin's interceptor chain, and log the
86
+ * effective config so the resolved card version and whether a webhook is
87
+ * wired up are obvious at boot.
88
+ */
89
+ setup(): Promise<void>;
90
+ /** Drop the shared runtime. Idempotent. */
91
+ shutdown(): Promise<void>;
92
+ /**
93
+ * Mount the card-building and card-posting routes under the plugin base
94
+ * path (`/api/teams`). `POST /card` is what the dev display page calls to
95
+ * preview a card live; `POST /post` pushes a compiled card to the configured
96
+ * Teams incoming webhook.
97
+ *
98
+ * Neither route is wrapped in `asUser(req)`: compiling a card is a pure
99
+ * transform of the request body and posting goes to a preconfigured webhook,
100
+ * so neither reads workspace data on the caller's behalf and neither needs an
101
+ * OBO token. Wrapping them would make the routes throw
102
+ * `AuthenticationError` whenever the user-token header is absent (a local
103
+ * `curl`, a health probe), which - since AppKit does not catch a rejection
104
+ * raised inside the handler - takes the process down rather than answering
105
+ * 401.
106
+ */
107
+ injectRoutes(router: IAppRouter): void;
108
+ exports(): {
109
+ /**
110
+ * Compile a card spec into an Adaptive Card document. For agent-driven
111
+ * builds use {@link teamsCardTool} instead.
112
+ */
113
+ buildCard: (spec: card.CardSpec, signal?: AbortSignal) => Promise<card.CardResult>;
114
+ /**
115
+ * Post a compiled card to the configured Teams incoming webhook. Throws
116
+ * when no webhook is configured.
117
+ */
118
+ postCard: (cardDocument: card.AdaptiveCard, signal?: AbortSignal) => Promise<void>;
119
+ };
120
+ /** AppKit `ToolProvider`: the tool definitions offered to an agent. */
121
+ getAgentTools(): AgentToolDefinition[];
122
+ /**
123
+ * AppKit `ToolProvider`: run one tool call. Arguments are validated against
124
+ * the tool's schema first, and a validation failure comes back as an
125
+ * LLM-friendly string so the model can correct itself on the next turn.
126
+ */
127
+ executeAgentTool(name: string, args: unknown, signal?: AbortSignal): Promise<unknown>;
128
+ /**
129
+ * Handle one inbound request from Azure Bot Service on `POST /messages`.
130
+ *
131
+ * The order of operations is dictated by how Bot Service behaves, not by
132
+ * convenience:
133
+ *
134
+ * 1. **Refuse when unconfigured.** With no `appId` there is no audience to
135
+ * validate a token against, so the endpoint cannot be operated safely;
136
+ * it answers 503 rather than processing an unauthenticated activity.
137
+ * 2. **Validate the JWT before parsing the body.** The token is the only
138
+ * trust boundary this endpoint has.
139
+ * 3. **Pin the reply destination to the token.** `serviceUrl` arrives in the
140
+ * body, and replies carry the bot's credentials, so it is only honored
141
+ * when it matches the verified token.
142
+ * 4. **Answer 200 immediately, then run the agent.** Bot Service times out
143
+ * an unacknowledged activity in seconds and RETRIES it; a card takes far
144
+ * longer than that, and a retry would post a duplicate card.
145
+ *
146
+ * Activities that carry no prompt (`typing`, `conversationUpdate`, an
147
+ * attachment-only message) are acknowledged and dropped - exactly what a bot
148
+ * does with them.
149
+ */
150
+ private handleMessage;
151
+ /** Compile a card, validating the request body against the spec schema. */
152
+ private executeBuild;
153
+ /**
154
+ * Run one conversation turn: validate the inbound activity, resolve the agent
155
+ * from the sibling agent plugin, and answer with card-carrying activities.
156
+ *
157
+ * Resolution failures are reported distinctly because they have different
158
+ * fixes: 503 when no agent plugin is mounted at all (a wiring problem), 404
159
+ * when the caller named an `agentId` that is not registered (a request
160
+ * problem).
161
+ */
162
+ private executeTurn;
163
+ /**
164
+ * Compile then post a card, validating the request body against the spec
165
+ * schema. The compile is folded INTO the executed callback so a throw from
166
+ * either half (a build failure, or `postCard` refusing because no webhook is
167
+ * configured) comes back as a failed {@link ExecutionResult} the route can
168
+ * answer with, rather than escaping the handler as an unhandled rejection.
169
+ */
170
+ private executePost;
171
+ }
172
+ /**
173
+ * Register the Teams plugin.
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * import { createApp, server } from "@databricks/appkit";
178
+ * import { plugin as teamsPlugin } from "@dbx-tools/teams";
179
+ *
180
+ * await createApp({
181
+ * plugins: [server(), teamsPlugin.teams()],
182
+ * });
183
+ * ```
184
+ */
185
+ export declare const teams: import("@databricks/appkit").ToPlugin<typeof TeamsPlugin, TeamsPluginConfig, "teams">;