@dbx-tools/shared-teams 0.3.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # @dbx-tools/shared-teams
2
+
3
+ Browser-safe Adaptive Card and Bot Framework activity schemas (plus inferred
4
+ types) for the Teams add-on.
5
+
6
+ Import this package when a UI, Mastra tool schema, server route, or test needs
7
+ to validate the same Adaptive Card payloads that
8
+ [`@dbx-tools/teams`](../../node/teams) builds and
9
+ [`@dbx-tools/ui-teams`](../../ui/teams) renders.
10
+
11
+ Key features:
12
+
13
+ - High-level `CardSpec` contract - the small, model-friendly vocabulary a model
14
+ drafts (title, subtitle, text, facts, link actions) instead of hand-authoring
15
+ raw Adaptive Card JSON.
16
+ - `AdaptiveCard` envelope schema for the compiled Adaptive Card 1.5 document the
17
+ `adaptivecards` renderer consumes and Teams accepts.
18
+ - `CardResult` schema for the build response returned to a model or a browser.
19
+ - `Activity` contract for the conversation endpoint - the Bot Framework envelope
20
+ a Teams channel exchanges, with `CardAttachment` tagging a card as
21
+ `application/vnd.microsoft.card.adaptive`, plus the `toCardAttachment` /
22
+ `cardsOf` helpers both sides use so attachments are wrapped and read
23
+ identically. Unknown activity fields are preserved rather than stripped, since
24
+ a real channel sends far more than this reads.
25
+ - Model/tool-friendly schemas that avoid JSON Schema constraints known to cause
26
+ problems with some serving endpoints (no array `minItems`).
27
+
28
+ ## Validate A Drafted Card
29
+
30
+ ```ts
31
+ import { card, type CardSpec } from "@dbx-tools/shared-teams";
32
+
33
+ const spec: CardSpec = card.cardSpecSchema.parse({
34
+ title: "Deployment succeeded",
35
+ subtitle: "prod • 2m ago",
36
+ text: "The **api** service rolled out cleanly.",
37
+ facts: [
38
+ { title: "Version", value: "1.4.2" },
39
+ { title: "Owner", value: "alice" },
40
+ ],
41
+ actions: [{ title: "View run", url: "https://example.com/runs/42" }],
42
+ });
43
+ ```
44
+
45
+ The spec schema is intentionally small so a model produces valid input rather
46
+ than free-form card JSON the renderer would reject.
47
+
48
+ ## Validate A Compiled Card
49
+
50
+ ```ts
51
+ const result = card.cardResultSchema.parse(await response.json());
52
+ // result.card is a full Adaptive Card document ready to render or post to Teams.
53
+ ```
54
+
55
+ `adaptiveCardSchema` pins the envelope every consumer relies on (`type`,
56
+ `$schema`, `version`, `body`); the builder in `@dbx-tools/teams` owns the exact
57
+ element shapes inside `body` / `actions`.
58
+
59
+ ## Modules
60
+
61
+ - `card` - `cardFactSchema`, `cardActionSchema`, `cardSpecSchema`,
62
+ `adaptiveCardSchema`, `cardResultSchema`, the `ADAPTIVE_CARD_VERSION` /
63
+ `ADAPTIVE_CARD_SCHEMA_URL` constants, and flat inferred types: `CardFact`,
64
+ `CardAction`, `CardSpec`, `AdaptiveCard`, and `CardResult`.
65
+ - `activity` - `activitySchema`, `activityRequestSchema`,
66
+ `activityResponseSchema`, `cardAttachmentSchema`, `channelAccountSchema`,
67
+ `conversationAccountSchema`, the `ADAPTIVE_CARD_CONTENT_TYPE` /
68
+ `ACTIVITY_TYPES` constants, the `toCardAttachment` / `cardsOf` helpers, and
69
+ the `Activity`, `ActivityRequest`, `ActivityResponse`, `CardAttachment`,
70
+ `ChannelAccount`, `ConversationAccount`, and `ActivityType` types.
71
+
72
+ The schemas intentionally avoid array `.min()` constraints so they can be reused
73
+ as model/tool JSON schemas for serving endpoints that reject `minItems`.
package/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+
5
+ export * as activity from "./src/activity";
6
+ export * as card from "./src/card";
7
+ export type { ActivityType, ChannelAccount, ConversationAccount, CardAttachment, Activity, ActivityRequest, ActivityResponse } from "./src/activity";
8
+ export type { CardFact, CardAction, CardSpec, AdaptiveCard, CardResult } from "./src/card";
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@dbx-tools/shared-teams",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/reggie-db/dbx-tools.git",
6
+ "directory": "workspaces/shared/teams"
7
+ },
8
+ "devDependencies": {
9
+ "@types/node": "^24.6.0",
10
+ "tsx": "^4.23.0",
11
+ "typescript": "^5.9.3"
12
+ },
13
+ "dependencies": {
14
+ "zod": "4.3.6",
15
+ "@dbx-tools/shared-core": "0.3.39"
16
+ },
17
+ "main": "index.ts",
18
+ "license": "UNLICENSED",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "version": "0.3.39",
23
+ "types": "index.ts",
24
+ "type": "module",
25
+ "exports": {
26
+ ".": "./index.ts",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "files": [
30
+ "index.ts",
31
+ "src"
32
+ ],
33
+ "dbxToolsConfig": {
34
+ "tags": [
35
+ "shared"
36
+ ]
37
+ },
38
+ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\".",
39
+ "scripts": {
40
+ "build": "projen build",
41
+ "compile": "projen compile",
42
+ "default": "projen default",
43
+ "package": "projen package",
44
+ "post-compile": "projen post-compile",
45
+ "pre-compile": "projen pre-compile",
46
+ "test": "projen test",
47
+ "watch": "projen watch",
48
+ "projen": "projen"
49
+ }
50
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Wire-format contract for the Teams conversation endpoint: the Bot Framework
3
+ * `Activity` a Teams-like client posts, and the activity (carrying Adaptive Card
4
+ * attachments) the agent answers with.
5
+ *
6
+ * Why this shape rather than a bespoke `{ message }` envelope: Teams does not
7
+ * speak a custom chat API. A channel delivers a bot a JSON `Activity` and reads
8
+ * back activities whose `attachments` carry
9
+ * `application/vnd.microsoft.card.adaptive` payloads. Modelling the real
10
+ * protocol here means the same endpoint that backs the in-repo preview chat is
11
+ * the one a Bot Framework channel could call - the analogue of how the Mastra
12
+ * plugin exposes MCP at a path rather than inventing a tool-call API.
13
+ *
14
+ * Only the subset the conversation endpoint actually reads or writes is
15
+ * modelled. `Activity` is a large, evolving envelope, so unknown fields are
16
+ * PRESERVED rather than stripped (see {@link activitySchema}) - a channel sends
17
+ * far more than this, and dropping it would corrupt a round-trip.
18
+ *
19
+ * @module
20
+ */
21
+
22
+ import { z } from "zod";
23
+ import { adaptiveCardSchema } from "./card";
24
+
25
+ /** The attachment content type Teams uses for an Adaptive Card. */
26
+ export const ADAPTIVE_CARD_CONTENT_TYPE = "application/vnd.microsoft.card.adaptive";
27
+
28
+ /** Bot Framework activity types this endpoint understands. */
29
+ export const ACTIVITY_TYPES = ["message", "conversationUpdate", "typing"] as const;
30
+
31
+ /** A Bot Framework activity type. */
32
+ export type ActivityType = (typeof ACTIVITY_TYPES)[number];
33
+
34
+ /**
35
+ * A participant in a conversation. `id` is the stable key; `name` is the
36
+ * display label a chat transcript shows.
37
+ */
38
+ export const channelAccountSchema = z.object({
39
+ id: z.string().describe("Stable id of the user or bot."),
40
+ name: z.string().optional().describe("Display name shown in the transcript."),
41
+ });
42
+
43
+ /** A conversation participant (user or bot). */
44
+ export type ChannelAccount = z.infer<typeof channelAccountSchema>;
45
+
46
+ /**
47
+ * The conversation an activity belongs to. The `id` is what maps onto a Mastra
48
+ * memory thread, so a client that keeps sending the same id gets a continuous
49
+ * conversation.
50
+ */
51
+ export const conversationAccountSchema = z.object({
52
+ id: z.string().describe("Conversation id; maps to the agent's memory thread."),
53
+ });
54
+
55
+ /** The conversation an activity belongs to. */
56
+ export type ConversationAccount = z.infer<typeof conversationAccountSchema>;
57
+
58
+ /**
59
+ * An Adaptive Card attachment - how a card reaches a Teams client. The card
60
+ * document lives under `content`, tagged by `contentType`.
61
+ */
62
+ export const cardAttachmentSchema = z.object({
63
+ contentType: z.literal(ADAPTIVE_CARD_CONTENT_TYPE),
64
+ content: adaptiveCardSchema.describe("The compiled Adaptive Card document."),
65
+ });
66
+
67
+ /** An Adaptive Card attachment on an activity. */
68
+ export type CardAttachment = z.infer<typeof cardAttachmentSchema>;
69
+
70
+ /**
71
+ * A Bot Framework activity.
72
+ *
73
+ * `.passthrough()` is deliberate: a real channel sends `channelId`,
74
+ * `serviceUrl`, `timestamp`, `replyToId`, `channelData` and more. This endpoint
75
+ * reads only what it needs, but an unknown field is part of the caller's
76
+ * envelope and is kept so a client can round-trip its own metadata.
77
+ */
78
+ export const activitySchema = z
79
+ .object({
80
+ type: z.enum(ACTIVITY_TYPES).describe("Activity type; `message` carries user text."),
81
+ id: z.string().optional().describe("Activity id, assigned by the sender."),
82
+ text: z.string().optional().describe("Message text, present on a `message` activity."),
83
+ from: channelAccountSchema.optional().describe("Who sent the activity."),
84
+ recipient: channelAccountSchema.optional().describe("Who the activity is addressed to."),
85
+ conversation: conversationAccountSchema
86
+ .optional()
87
+ .describe("Conversation the activity belongs to."),
88
+ attachments: z
89
+ .array(cardAttachmentSchema)
90
+ .optional()
91
+ .describe("Adaptive Card attachments carried by the activity."),
92
+ timestamp: z.string().optional().describe("ISO-8601 time the activity was created."),
93
+ })
94
+ .passthrough();
95
+
96
+ /** A Bot Framework activity. */
97
+ export type Activity = z.infer<typeof activitySchema>;
98
+
99
+ /**
100
+ * The request body the conversation endpoint accepts: an inbound activity plus
101
+ * optional routing hints that are NOT part of the Bot Framework envelope
102
+ * (which agent to run, which serving model to use). Keeping them as siblings of
103
+ * `activity` leaves the protocol payload untouched.
104
+ */
105
+ export const activityRequestSchema = z.object({
106
+ activity: activitySchema.describe("The inbound Bot Framework activity."),
107
+ agentId: z
108
+ .string()
109
+ .optional()
110
+ .describe("Mastra agent to answer with. Defaults to the plugin's default agent."),
111
+ model: z.string().optional().describe("Optional serving-endpoint override for this turn."),
112
+ });
113
+
114
+ /** A request to the Teams conversation endpoint. */
115
+ export type ActivityRequest = z.infer<typeof activityRequestSchema>;
116
+
117
+ /**
118
+ * The endpoint's response: the activities to append to the transcript. An array
119
+ * because one turn may answer with several activities (Teams itself allows a
120
+ * bot to send more than one reply), and because a future streaming/typing
121
+ * variant can grow into the same shape without a breaking change.
122
+ */
123
+ export const activityResponseSchema = z.object({
124
+ activities: z.array(activitySchema).describe("Activities the bot replies with."),
125
+ });
126
+
127
+ /** The response from the Teams conversation endpoint. */
128
+ export type ActivityResponse = z.infer<typeof activityResponseSchema>;
129
+
130
+ /**
131
+ * Wrap a compiled Adaptive Card as a Teams attachment.
132
+ *
133
+ * Browser-safe and shared so the server (building a reply) and any client
134
+ * (rendering an optimistic local activity) tag attachments identically.
135
+ */
136
+ export const toCardAttachment = (card: z.infer<typeof adaptiveCardSchema>): CardAttachment => ({
137
+ contentType: ADAPTIVE_CARD_CONTENT_TYPE,
138
+ content: card,
139
+ });
140
+
141
+ /** Read the Adaptive Card documents carried by an activity, if any. */
142
+ export const cardsOf = (activity: Activity): z.infer<typeof adaptiveCardSchema>[] =>
143
+ (activity.attachments ?? [])
144
+ .filter((attachment) => attachment.contentType === ADAPTIVE_CARD_CONTENT_TYPE)
145
+ .map((attachment) => attachment.content);
package/src/card.ts ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Wire-format contract for the Teams add-on: the semantic card a model drafts
3
+ * and the Adaptive Card document that drafting compiles to. Pure (zod +
4
+ * inferred types, no Node-only imports) so the server-side card builder, the
5
+ * Mastra tool, and the React Adaptive Cards renderer all validate / type
6
+ * against one definition.
7
+ *
8
+ * The model does NOT hand-author raw Adaptive Card JSON. It describes a card in
9
+ * a small, high-level vocabulary ({@link CardSpec}) - a title, some text/fact
10
+ * blocks, and optional link actions - and the builder compiles that into a
11
+ * valid Adaptive Card 1.5 document ({@link AdaptiveCard}). Keeping the model's
12
+ * surface small avoids the two failure modes of free-form card JSON: an invalid
13
+ * schema the renderer rejects, and a card that renders but ignores Teams' host
14
+ * constraints.
15
+ *
16
+ * Array fields intentionally avoid `.min()` / `.nonempty()`: those emit
17
+ * `minItems` in the JSON schema, which some Model Serving endpoints reject
18
+ * ("array types do not support minItems") when the schema is forwarded as a
19
+ * tool definition.
20
+ *
21
+ * @module
22
+ */
23
+
24
+ import { z } from "zod";
25
+
26
+ /** The Adaptive Card schema version the builder targets. Teams supports 1.5. */
27
+ export const ADAPTIVE_CARD_VERSION = "1.5";
28
+
29
+ /** The Adaptive Card `$schema` URL a well-formed document declares. */
30
+ export const ADAPTIVE_CARD_SCHEMA_URL = "http://adaptivecards.io/schemas/adaptive-card.json";
31
+
32
+ /**
33
+ * A single labelled fact, rendered as a row in an Adaptive Card `FactSet`.
34
+ * Facts are the right shape for compact key/value detail (status, owner, due
35
+ * date) that would be noise as prose.
36
+ */
37
+ export const cardFactSchema = z.object({
38
+ title: z.string().describe('Fact label shown on the left (e.g. "Status", "Owner").'),
39
+ value: z.string().describe('Fact value shown on the right (e.g. "Open", "alice").'),
40
+ });
41
+
42
+ /** A labelled key/value fact in a {@link CardSpec}. */
43
+ export type CardFact = z.infer<typeof cardFactSchema>;
44
+
45
+ /**
46
+ * An action button rendered under the card body. Only an open-URL action is
47
+ * modelled: it is the one action that is safe to render in any host without a
48
+ * back-end wired up, and it covers the common "here is the link" case.
49
+ */
50
+ export const cardActionSchema = z.object({
51
+ title: z.string().describe('Button label (e.g. "Open ticket", "View run").'),
52
+ url: z
53
+ .string()
54
+ .describe("Absolute https URL the button opens when tapped (an Action.OpenUrl target)."),
55
+ });
56
+
57
+ /** A link action button in a {@link CardSpec}. */
58
+ export type CardAction = z.infer<typeof cardActionSchema>;
59
+
60
+ /**
61
+ * The high-level card a model asks to build (the tool input). Deliberately
62
+ * small: a heading, optional supporting text, optional facts, and optional link
63
+ * buttons. The builder turns this into a full Adaptive Card document.
64
+ */
65
+ export const cardSpecSchema = z.object({
66
+ title: z.string().describe("Card heading, shown bold at the top of the card."),
67
+ subtitle: z
68
+ .string()
69
+ .optional()
70
+ .describe("Optional lighter subheading under the title (e.g. a category or timestamp)."),
71
+ text: z
72
+ .string()
73
+ .optional()
74
+ .describe(
75
+ [
76
+ "Optional body text under the heading. A limited Markdown subset is",
77
+ "supported by the Teams host: **bold**, _italic_, links, and '-' bullet",
78
+ "lists. Do NOT use headings, tables, or fenced code blocks - use the",
79
+ "`facts` array for tabular key/value detail instead.",
80
+ ].join(" "),
81
+ ),
82
+ facts: z
83
+ .array(cardFactSchema)
84
+ .optional()
85
+ .describe("Optional key/value rows rendered as a FactSet (label on the left, value right)."),
86
+ actions: z
87
+ .array(cardActionSchema)
88
+ .optional()
89
+ .describe("Optional link buttons rendered under the body, each opening a URL."),
90
+ });
91
+
92
+ /** The validated semantic card a model asked to build. */
93
+ export type CardSpec = z.infer<typeof cardSpecSchema>;
94
+
95
+ /**
96
+ * The compiled Adaptive Card document - the JSON the `adaptivecards` renderer
97
+ * consumes. Typed loosely (`elements` / `actions` as record arrays) because the
98
+ * full Adaptive Card schema is large and host-versioned; the builder owns the
99
+ * exact element shapes and this contract only pins the envelope every consumer
100
+ * relies on (`type`, `version`, `body`).
101
+ */
102
+ export const adaptiveCardSchema = z.object({
103
+ type: z.literal("AdaptiveCard"),
104
+ $schema: z.string(),
105
+ version: z.string(),
106
+ body: z.array(z.record(z.string(), z.unknown())),
107
+ actions: z.array(z.record(z.string(), z.unknown())).optional(),
108
+ });
109
+
110
+ /** A compiled Adaptive Card document ready to render or post to Teams. */
111
+ export type AdaptiveCard = z.infer<typeof adaptiveCardSchema>;
112
+
113
+ /**
114
+ * The result the card tool returns: the compiled Adaptive Card plus the
115
+ * `title` echoed back for a caller that wants a label without re-reading the
116
+ * document. Kept separate from {@link AdaptiveCard} so the tool output stays
117
+ * additive.
118
+ */
119
+ export const cardResultSchema = z.object({
120
+ title: z.string().describe("The card title, echoed for convenience."),
121
+ card: adaptiveCardSchema.describe("The compiled Adaptive Card document."),
122
+ });
123
+
124
+ /** The result of building a card. */
125
+ export type CardResult = z.infer<typeof cardResultSchema>;