@hahnfeld/teams-adapter 1.0.9

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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +286 -0
  3. package/dist/activity.d.ts +23 -0
  4. package/dist/activity.d.ts.map +1 -0
  5. package/dist/activity.js +3 -0
  6. package/dist/activity.js.map +1 -0
  7. package/dist/adapter.d.ts +174 -0
  8. package/dist/adapter.d.ts.map +1 -0
  9. package/dist/adapter.js +1583 -0
  10. package/dist/adapter.js.map +1 -0
  11. package/dist/app-package.d.ts +7 -0
  12. package/dist/app-package.d.ts.map +1 -0
  13. package/dist/app-package.js +158 -0
  14. package/dist/app-package.js.map +1 -0
  15. package/dist/assistant.d.ts +7 -0
  16. package/dist/assistant.d.ts.map +1 -0
  17. package/dist/assistant.js +32 -0
  18. package/dist/assistant.js.map +1 -0
  19. package/dist/commands/admin.d.ts +27 -0
  20. package/dist/commands/admin.d.ts.map +1 -0
  21. package/dist/commands/admin.js +146 -0
  22. package/dist/commands/admin.js.map +1 -0
  23. package/dist/commands/agents.d.ts +13 -0
  24. package/dist/commands/agents.d.ts.map +1 -0
  25. package/dist/commands/agents.js +98 -0
  26. package/dist/commands/agents.js.map +1 -0
  27. package/dist/commands/doctor.d.ts +8 -0
  28. package/dist/commands/doctor.d.ts.map +1 -0
  29. package/dist/commands/doctor.js +49 -0
  30. package/dist/commands/doctor.js.map +1 -0
  31. package/dist/commands/index.d.ts +16 -0
  32. package/dist/commands/index.d.ts.map +1 -0
  33. package/dist/commands/index.js +253 -0
  34. package/dist/commands/index.js.map +1 -0
  35. package/dist/commands/integrate.d.ts +8 -0
  36. package/dist/commands/integrate.d.ts.map +1 -0
  37. package/dist/commands/integrate.js +45 -0
  38. package/dist/commands/integrate.js.map +1 -0
  39. package/dist/commands/menu.d.ts +16 -0
  40. package/dist/commands/menu.d.ts.map +1 -0
  41. package/dist/commands/menu.js +92 -0
  42. package/dist/commands/menu.js.map +1 -0
  43. package/dist/commands/new-session.d.ts +13 -0
  44. package/dist/commands/new-session.d.ts.map +1 -0
  45. package/dist/commands/new-session.js +105 -0
  46. package/dist/commands/new-session.js.map +1 -0
  47. package/dist/commands/session.d.ts +22 -0
  48. package/dist/commands/session.d.ts.map +1 -0
  49. package/dist/commands/session.js +110 -0
  50. package/dist/commands/session.js.map +1 -0
  51. package/dist/commands/settings.d.ts +8 -0
  52. package/dist/commands/settings.d.ts.map +1 -0
  53. package/dist/commands/settings.js +54 -0
  54. package/dist/commands/settings.js.map +1 -0
  55. package/dist/conversation-store.d.ts +38 -0
  56. package/dist/conversation-store.d.ts.map +1 -0
  57. package/dist/conversation-store.js +101 -0
  58. package/dist/conversation-store.js.map +1 -0
  59. package/dist/draft-manager.d.ts +47 -0
  60. package/dist/draft-manager.d.ts.map +1 -0
  61. package/dist/draft-manager.js +136 -0
  62. package/dist/draft-manager.js.map +1 -0
  63. package/dist/formatting.d.ts +121 -0
  64. package/dist/formatting.d.ts.map +1 -0
  65. package/dist/formatting.js +392 -0
  66. package/dist/formatting.js.map +1 -0
  67. package/dist/graph.d.ts +59 -0
  68. package/dist/graph.d.ts.map +1 -0
  69. package/dist/graph.js +261 -0
  70. package/dist/graph.js.map +1 -0
  71. package/dist/index.d.ts +16 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +10 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/media.d.ts +29 -0
  76. package/dist/media.d.ts.map +1 -0
  77. package/dist/media.js +120 -0
  78. package/dist/media.js.map +1 -0
  79. package/dist/permissions.d.ts +15 -0
  80. package/dist/permissions.d.ts.map +1 -0
  81. package/dist/permissions.js +221 -0
  82. package/dist/permissions.js.map +1 -0
  83. package/dist/plugin.d.ts +13 -0
  84. package/dist/plugin.d.ts.map +1 -0
  85. package/dist/plugin.js +689 -0
  86. package/dist/plugin.js.map +1 -0
  87. package/dist/renderer.d.ts +49 -0
  88. package/dist/renderer.d.ts.map +1 -0
  89. package/dist/renderer.js +55 -0
  90. package/dist/renderer.js.map +1 -0
  91. package/dist/send-utils.d.ts +15 -0
  92. package/dist/send-utils.d.ts.map +1 -0
  93. package/dist/send-utils.js +64 -0
  94. package/dist/send-utils.js.map +1 -0
  95. package/dist/task-modules.d.ts +34 -0
  96. package/dist/task-modules.d.ts.map +1 -0
  97. package/dist/task-modules.js +136 -0
  98. package/dist/task-modules.js.map +1 -0
  99. package/dist/types.d.ts +26 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +3 -0
  102. package/dist/types.js.map +1 -0
  103. package/dist/validators.d.ts +54 -0
  104. package/dist/validators.d.ts.map +1 -0
  105. package/dist/validators.js +142 -0
  106. package/dist/validators.js.map +1 -0
  107. package/package.json +56 -0
@@ -0,0 +1,136 @@
1
+ import { log } from "@openacp/plugin-sdk";
2
+ import { splitMessage } from "./formatting.js";
3
+ import { sendText } from "./send-utils.js";
4
+ /** First flush fires quickly so the user sees content fast */
5
+ const FIRST_FLUSH_INTERVAL = 500;
6
+ /** Subsequent flushes are slower to avoid rate limits (Teams: 7 ops/sec/conversation) */
7
+ const UPDATE_FLUSH_INTERVAL = 3000;
8
+ const MAX_DISPLAY_LENGTH = 1900;
9
+ export class TeamsMessageDraft {
10
+ context;
11
+ sendQueue;
12
+ sessionId;
13
+ buffer = "";
14
+ ref;
15
+ flushTimer;
16
+ flushPromise = Promise.resolve();
17
+ lastSentBuffer = "";
18
+ displayTruncated = false;
19
+ firstFlushPending = false;
20
+ finalizing = false;
21
+ constructor(context, sendQueue, sessionId) {
22
+ this.context = context;
23
+ this.sendQueue = sendQueue;
24
+ this.sessionId = sessionId;
25
+ }
26
+ /** Update the TurnContext to the latest inbound turn (prevents stale context refs) */
27
+ updateContext(context) {
28
+ this.context = context;
29
+ }
30
+ append(text) {
31
+ if (!text)
32
+ return;
33
+ this.buffer += text;
34
+ this.scheduleFlush();
35
+ }
36
+ getBuffer() {
37
+ return this.buffer;
38
+ }
39
+ scheduleFlush() {
40
+ if (this.flushTimer)
41
+ return;
42
+ // Fast first flush (500ms) for perceived responsiveness, then slower updates (3s)
43
+ const interval = this.ref?.activityId ? UPDATE_FLUSH_INTERVAL : FIRST_FLUSH_INTERVAL;
44
+ this.flushTimer = setTimeout(() => {
45
+ this.flushTimer = undefined;
46
+ this.flushPromise = this.flushPromise.then(() => this.flush()).catch(() => { });
47
+ }, interval);
48
+ }
49
+ async flush() {
50
+ // Streaming updates via updateActivity don't work with the teams.apps SDK
51
+ // context (it only has send(), not updateActivity()). Instead of trying to
52
+ // update in-place, we skip periodic flushes and let finalize() send the
53
+ // complete message. The typing indicator keeps the user informed.
54
+ }
55
+ async stripPattern(pattern) {
56
+ if (!this.buffer)
57
+ return;
58
+ try {
59
+ this.buffer = this.buffer.replace(pattern, "").trim();
60
+ }
61
+ catch {
62
+ // Regex failed — leave buffer unchanged
63
+ }
64
+ }
65
+ async finalize() {
66
+ if (this.finalizing)
67
+ return;
68
+ this.finalizing = true;
69
+ try {
70
+ await this._finalizeInner();
71
+ }
72
+ finally {
73
+ this.finalizing = false;
74
+ }
75
+ }
76
+ async _finalizeInner() {
77
+ if (this.flushTimer) {
78
+ clearTimeout(this.flushTimer);
79
+ this.flushTimer = undefined;
80
+ }
81
+ if (!this.buffer)
82
+ return;
83
+ // Send the complete text as new message(s), split if needed.
84
+ // We don't use updateActivity because the teams.apps SDK context
85
+ // doesn't support it — only send() is available.
86
+ const chunks = splitMessage(this.buffer, MAX_DISPLAY_LENGTH);
87
+ for (let i = 0; i < chunks.length; i++) {
88
+ const content = chunks[i];
89
+ try {
90
+ await this.sendQueue.enqueue(() => sendText(this.context, content), { type: "other" });
91
+ }
92
+ catch (err) {
93
+ log.warn({ err, sessionId: this.sessionId, chunk: i }, "[TeamsMessageDraft] finalize: chunk send failed");
94
+ }
95
+ }
96
+ }
97
+ }
98
+ export class TeamsDraftManager {
99
+ sendQueue;
100
+ drafts = new Map();
101
+ constructor(sendQueue) {
102
+ this.sendQueue = sendQueue;
103
+ }
104
+ getOrCreate(sessionId, context) {
105
+ let draft = this.drafts.get(sessionId);
106
+ if (!draft) {
107
+ draft = new TeamsMessageDraft(context, this.sendQueue, sessionId);
108
+ this.drafts.set(sessionId, draft);
109
+ }
110
+ else {
111
+ // Re-bind to the latest TurnContext to prevent stale context references
112
+ // when a new inbound message arrives on the same session (new turn).
113
+ draft.updateContext(context);
114
+ }
115
+ return draft;
116
+ }
117
+ hasDraft(sessionId) {
118
+ return this.drafts.has(sessionId);
119
+ }
120
+ getDraft(sessionId) {
121
+ return this.drafts.get(sessionId);
122
+ }
123
+ async finalize(sessionId, _context, _isAssistant) {
124
+ const draft = this.drafts.get(sessionId);
125
+ if (!draft)
126
+ return;
127
+ // Delete BEFORE awaiting to prevent concurrent finalize() calls
128
+ // from double-finalizing the same draft (matches Telegram pattern)
129
+ this.drafts.delete(sessionId);
130
+ await draft.finalize();
131
+ }
132
+ cleanup(sessionId) {
133
+ this.drafts.delete(sessionId);
134
+ }
135
+ }
136
+ //# sourceMappingURL=draft-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-manager.js","sourceRoot":"","sources":["../src/draft-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ export type { OutputMode, ToolDisplaySpec, ToolCardSnapshot, PlanEntry } from "@openacp/plugin-sdk";
2
+ export declare const STATUS_ICONS: Record<string, string>;
3
+ export declare const KIND_ICONS: Record<string, string>;
4
+ export declare function progressBar(ratio: number, length?: number): string;
5
+ export declare function formatTokens(n: number): string;
6
+ export declare function truncateContent(text: string, max: number): string;
7
+ /**
8
+ * Split text into chunks that fit within maxLength, preferring paragraph
9
+ * and line boundaries. Handles single lines longer than maxLength by
10
+ * hard-splitting at the limit.
11
+ */
12
+ export declare function splitMessage(text: string, maxLength: number): string[];
13
+ export declare function extractContentText(content: unknown): string;
14
+ export declare function stripCodeFences(text: string): string;
15
+ export declare function resolveToolIcon(kind: string, displayKind?: string, status?: string): string;
16
+ export declare function formatToolTitle(name: string, _rawInput?: unknown, displayTitle?: string): string;
17
+ export declare function formatToolSummary(name: string, rawInput?: unknown, displaySummary?: string): string;
18
+ export declare function formatToolCall(tool: {
19
+ id: string;
20
+ name: string;
21
+ kind?: string;
22
+ status?: string;
23
+ content?: unknown;
24
+ rawInput?: unknown;
25
+ viewerLinks?: {
26
+ file?: string;
27
+ diff?: string;
28
+ };
29
+ viewerFilePath?: string;
30
+ displaySummary?: string;
31
+ displayTitle?: string;
32
+ displayKind?: string;
33
+ }, verbosity?: "low" | "medium" | "high"): string;
34
+ export declare function formatToolUpdate(tool: {
35
+ id: string;
36
+ name: string;
37
+ kind?: string;
38
+ status?: string;
39
+ content?: unknown;
40
+ rawInput?: unknown;
41
+ viewerLinks?: {
42
+ file?: string;
43
+ diff?: string;
44
+ };
45
+ viewerFilePath?: string;
46
+ displaySummary?: string;
47
+ displayTitle?: string;
48
+ displayKind?: string;
49
+ }, verbosity?: "low" | "medium" | "high"): string;
50
+ export declare function formatPlan(entries: {
51
+ content: string;
52
+ status: string;
53
+ }[], verbosity?: "low" | "medium" | "high"): string;
54
+ export declare function formatUsage(usage: {
55
+ tokensUsed?: number;
56
+ contextSize?: number;
57
+ cost?: number;
58
+ }, verbosity?: "low" | "medium" | "high"): string;
59
+ interface UsageData {
60
+ tokensUsed?: number;
61
+ contextSize?: number;
62
+ cost?: number;
63
+ duration?: number;
64
+ }
65
+ /** Structured usage card with FactSet and visual progress indicator */
66
+ export declare function renderUsageCard(usage: UsageData, mode: "low" | "medium" | "high"): {
67
+ body: unknown[];
68
+ };
69
+ interface ToolCallCardMeta {
70
+ id: string;
71
+ name: string;
72
+ kind?: string;
73
+ status?: string;
74
+ rawInput?: unknown;
75
+ content?: unknown;
76
+ displaySummary?: string;
77
+ displayTitle?: string;
78
+ displayKind?: string;
79
+ viewerLinks?: {
80
+ file?: string;
81
+ diff?: string;
82
+ };
83
+ viewerFilePath?: string;
84
+ }
85
+ /** Structured tool call card with icon, name, status, and optional details */
86
+ export declare function renderToolCallCard(tool: ToolCallCardMeta, verbosity: "low" | "medium" | "high"): {
87
+ body: unknown[];
88
+ actions?: unknown[];
89
+ };
90
+ /** Structured plan card with status-colored rows */
91
+ export declare function renderPlanCard(entries: {
92
+ content: string;
93
+ status: string;
94
+ }[], verbosity: "low" | "medium" | "high"): {
95
+ body: unknown[];
96
+ };
97
+ interface CitationSource {
98
+ /** File path or tool name */
99
+ name: string;
100
+ /** URL to view the file or diff */
101
+ url: string;
102
+ /** Short description or content snippet */
103
+ abstract?: string;
104
+ /** Icon type: "Source Code", "PDF", "Image", etc. */
105
+ iconType?: string;
106
+ }
107
+ /**
108
+ * Build Teams citation entities from file references.
109
+ *
110
+ * Teams displays citations as numbered in-text references [1] with hover popups
111
+ * showing the source name, description, and link. Max 20 per message.
112
+ *
113
+ * @see https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bot-messages-ai-generated-content
114
+ */
115
+ export declare function buildCitationEntities(sources: CitationSource[]): unknown[];
116
+ /**
117
+ * Build citation text references like "[1]" to insert into message text.
118
+ * Returns a suffix string to append to the message.
119
+ */
120
+ export declare function buildCitationSuffix(sources: CitationSource[]): string;
121
+ //# sourceMappingURL=formatting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatting.d.ts","sourceRoot":"","sources":["../src/formatting.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,392 @@
1
+ // Status/Kind constants
2
+ export const STATUS_ICONS = {
3
+ pending: "⏳", in_progress: "🔄", completed: "✅", failed: "❌",
4
+ cancelled: "🚫", running: "🔄", done: "✅", error: "❌",
5
+ };
6
+ export const KIND_ICONS = {
7
+ read: "📖", edit: "✏️", write: "✏️", delete: "🗑️", execute: "▶️",
8
+ command: "▶️", bash: "▶️", terminal: "▶️", search: "🔍", web: "🌐",
9
+ fetch: "🌐", agent: "🧠", think: "🧠", install: "📦", move: "📦", other: "🛠️",
10
+ };
11
+ export function progressBar(ratio, length = 10) {
12
+ const filled = Math.round(Math.min(1, Math.max(0, ratio)) * length);
13
+ return "▓".repeat(filled) + "░".repeat(length - filled);
14
+ }
15
+ export function formatTokens(n) {
16
+ if (n >= 1000)
17
+ return `${Math.round(n / 1000)}k`;
18
+ return String(n);
19
+ }
20
+ export function truncateContent(text, max) {
21
+ const suffix = "… (truncated)";
22
+ if (text.length <= max)
23
+ return text;
24
+ return text.slice(0, max - suffix.length) + suffix;
25
+ }
26
+ /**
27
+ * Split text into chunks that fit within maxLength, preferring paragraph
28
+ * and line boundaries. Handles single lines longer than maxLength by
29
+ * hard-splitting at the limit.
30
+ */
31
+ export function splitMessage(text, maxLength) {
32
+ if (text.length <= maxLength)
33
+ return [text];
34
+ const paragraphs = text.split("\n\n");
35
+ const chunks = [];
36
+ let current = "";
37
+ const pushCurrent = () => {
38
+ if (current) {
39
+ chunks.push(current);
40
+ current = "";
41
+ }
42
+ };
43
+ // Hard-split a single string that exceeds maxLength
44
+ const hardSplit = (s) => {
45
+ while (s.length > maxLength) {
46
+ chunks.push(s.slice(0, maxLength));
47
+ s = s.slice(maxLength);
48
+ }
49
+ return s; // remainder
50
+ };
51
+ for (const para of paragraphs) {
52
+ const candidate = current ? `${current}\n\n${para}` : para;
53
+ if (candidate.length <= maxLength) {
54
+ current = candidate;
55
+ continue;
56
+ }
57
+ // Candidate exceeds limit — flush current and process para separately
58
+ pushCurrent();
59
+ if (para.length <= maxLength) {
60
+ current = para;
61
+ continue;
62
+ }
63
+ // Para exceeds limit — try splitting on line boundaries
64
+ const lines = para.split("\n");
65
+ for (const line of lines) {
66
+ if (line.length > maxLength) {
67
+ // Line itself exceeds limit — hard-split it
68
+ pushCurrent();
69
+ current = hardSplit(line);
70
+ continue;
71
+ }
72
+ const lineCandidate = current ? `${current}\n${line}` : line;
73
+ if (lineCandidate.length > maxLength) {
74
+ pushCurrent();
75
+ current = line;
76
+ }
77
+ else {
78
+ current = lineCandidate;
79
+ }
80
+ }
81
+ }
82
+ if (current)
83
+ chunks.push(current);
84
+ return chunks;
85
+ }
86
+ export function extractContentText(content) {
87
+ if (content == null)
88
+ return "";
89
+ if (typeof content === "string")
90
+ return content;
91
+ if (Array.isArray(content)) {
92
+ return content.map((c) => {
93
+ if (typeof c === "string")
94
+ return c;
95
+ if (c && typeof c === "object" && "text" in c)
96
+ return String(c.text);
97
+ return "";
98
+ }).filter(Boolean).join("\n");
99
+ }
100
+ if (typeof content === "object" && content !== null && "text" in content) {
101
+ return String(content.text);
102
+ }
103
+ return "";
104
+ }
105
+ export function stripCodeFences(text) {
106
+ return text.replace(/^```[\w]*\n?/gm, "").replace(/\n?```$/gm, "").trim();
107
+ }
108
+ export function resolveToolIcon(kind, displayKind, status) {
109
+ if (status && STATUS_ICONS[status])
110
+ return STATUS_ICONS[status];
111
+ if (displayKind && KIND_ICONS[displayKind])
112
+ return KIND_ICONS[displayKind];
113
+ if (kind && KIND_ICONS[kind])
114
+ return KIND_ICONS[kind];
115
+ return "🔧";
116
+ }
117
+ export function formatToolTitle(name, _rawInput, displayTitle) {
118
+ if (displayTitle)
119
+ return displayTitle;
120
+ return name;
121
+ }
122
+ export function formatToolSummary(name, rawInput, displaySummary) {
123
+ if (displaySummary)
124
+ return displaySummary;
125
+ if (rawInput && typeof rawInput === "object") {
126
+ const input = rawInput;
127
+ if (input.pattern)
128
+ return `${name} "${input.pattern}"`;
129
+ if (input.file_path)
130
+ return `${name} ${input.file_path}`;
131
+ }
132
+ return name;
133
+ }
134
+ export function formatToolCall(tool, verbosity = "medium") {
135
+ const si = resolveToolIcon(tool.kind ?? "", tool.displayKind, tool.status);
136
+ const name = tool.name || "Tool";
137
+ const label = verbosity === "low"
138
+ ? formatToolTitle(name, tool.rawInput, tool.displayTitle)
139
+ : formatToolSummary(name, tool.rawInput, tool.displaySummary);
140
+ let text = `${si} **${label}**`;
141
+ if (tool.viewerLinks) {
142
+ const fn = tool.viewerFilePath ? tool.viewerFilePath.split("/").pop() || tool.viewerFilePath : "";
143
+ const fileLink = tool.viewerLinks.file;
144
+ const diffLink = tool.viewerLinks.diff;
145
+ if (fileLink) {
146
+ text += "\n[View " + (fn || "file") + "](" + fileLink + ")";
147
+ }
148
+ if (diffLink) {
149
+ text += "\n[View diff" + (fn ? " - " + fn : "") + "](" + diffLink + ")";
150
+ }
151
+ }
152
+ if (verbosity === "high" && (tool.rawInput || tool.content)) {
153
+ const maxLen = 500;
154
+ let detail = "";
155
+ if (tool.rawInput) {
156
+ const inputStr = typeof tool.rawInput === "string" ? tool.rawInput : JSON.stringify(tool.rawInput, null, 2);
157
+ if (inputStr && inputStr !== "{}") {
158
+ detail += `\n**Input:**\n\`\`\`\n${truncateContent(inputStr, maxLen)}\n\`\`\``;
159
+ }
160
+ }
161
+ const contentText = stripCodeFences(extractContentText(tool.content));
162
+ if (contentText) {
163
+ detail += `\n**Output:**\n\`\`\`\n${truncateContent(contentText, maxLen)}\n\`\`\``;
164
+ }
165
+ text += detail;
166
+ }
167
+ return text;
168
+ }
169
+ export function formatToolUpdate(tool, verbosity = "medium") {
170
+ return formatToolCall(tool, verbosity);
171
+ }
172
+ export function formatPlan(entries, verbosity = "medium") {
173
+ if (verbosity === "medium") {
174
+ const done = entries.filter((e) => e.status === "completed").length;
175
+ return `📋 **Plan:** ${done}/${entries.length} steps completed`;
176
+ }
177
+ const statusIconMap = {
178
+ pending: "⏳", in_progress: "🔄", completed: "✅",
179
+ };
180
+ const lines = entries.map((e, i) => `${statusIconMap[e.status] || "⬜"} ${i + 1}. ${e.content}`);
181
+ return `**Plan:**\n${lines.join("\n")}`;
182
+ }
183
+ export function formatUsage(usage, verbosity = "medium") {
184
+ const { tokensUsed, contextSize, cost } = usage;
185
+ if (tokensUsed == null)
186
+ return "📊 Usage data unavailable";
187
+ if (verbosity === "medium") {
188
+ const costStr = cost != null ? ` · $${cost.toFixed(2)}` : "";
189
+ return `📊 ${formatTokens(tokensUsed)} tokens${costStr}`;
190
+ }
191
+ if (contextSize == null)
192
+ return `📊 ${formatTokens(tokensUsed)} tokens`;
193
+ const ratio = tokensUsed / contextSize;
194
+ const pct = Math.round(ratio * 100);
195
+ const bar = progressBar(ratio);
196
+ const emoji = pct >= 85 ? "⚠️" : "📊";
197
+ let text = `${emoji} ${formatTokens(tokensUsed)} / ${formatTokens(contextSize)} tokens\n${bar} ${pct}%`;
198
+ if (cost != null)
199
+ text += `\n💰 $${cost.toFixed(2)}`;
200
+ return text;
201
+ }
202
+ /** Structured usage card with FactSet and visual progress indicator */
203
+ export function renderUsageCard(usage, mode) {
204
+ const { tokensUsed, contextSize, cost, duration } = usage;
205
+ if (tokensUsed == null) {
206
+ return { body: [{ type: "TextBlock", text: "📊 Usage data unavailable", wrap: true, isSubtle: true }] };
207
+ }
208
+ if (mode === "low") {
209
+ return { body: [{ type: "TextBlock", text: `📊 ${formatTokens(tokensUsed)} tokens`, wrap: true }] };
210
+ }
211
+ const body = [];
212
+ // Header
213
+ const pct = contextSize ? Math.round((tokensUsed / contextSize) * 100) : null;
214
+ const headerColor = pct != null && pct >= 85 ? "Attention" : "Default";
215
+ body.push({ type: "TextBlock", text: "📊 Usage Summary", weight: "Bolder", size: "Medium", color: headerColor });
216
+ // Facts
217
+ const facts = [
218
+ { title: "Tokens", value: formatTokens(tokensUsed) },
219
+ ];
220
+ if (contextSize)
221
+ facts.push({ title: "Context", value: `${formatTokens(contextSize)} (${pct}%)` });
222
+ if (cost != null)
223
+ facts.push({ title: "Cost", value: `$${cost.toFixed(4)}` });
224
+ if (duration != null)
225
+ facts.push({ title: "Duration", value: `${(duration / 1000).toFixed(1)}s` });
226
+ body.push({ type: "FactSet", facts, separator: true });
227
+ // Visual progress bar (context usage)
228
+ if (contextSize && mode === "high") {
229
+ body.push({
230
+ type: "TextBlock",
231
+ text: `\`${progressBar(tokensUsed / contextSize, 20)}\` ${pct}%`,
232
+ fontType: "Monospace",
233
+ size: "Small",
234
+ isSubtle: true,
235
+ });
236
+ }
237
+ return { body };
238
+ }
239
+ /** Structured tool call card with icon, name, status, and optional details */
240
+ export function renderToolCallCard(tool, verbosity) {
241
+ const icon = resolveToolIcon(tool.kind ?? "", tool.displayKind, tool.status);
242
+ const name = tool.name || "Tool";
243
+ const label = verbosity === "low"
244
+ ? formatToolTitle(name, tool.rawInput, tool.displayTitle)
245
+ : formatToolSummary(name, tool.rawInput, tool.displaySummary);
246
+ const statusColor = tool.status === "completed" ? "Good" : tool.status === "error" ? "Attention" : "Default";
247
+ const body = [];
248
+ // Header row: icon + tool name + status
249
+ body.push({
250
+ type: "ColumnSet",
251
+ columns: [
252
+ {
253
+ type: "Column",
254
+ width: "auto",
255
+ items: [{ type: "TextBlock", text: icon, size: "Medium" }],
256
+ },
257
+ {
258
+ type: "Column",
259
+ width: "stretch",
260
+ items: [
261
+ { type: "TextBlock", text: `**${label}**`, wrap: true },
262
+ ...(tool.status ? [{ type: "TextBlock", text: tool.status, size: "Small", isSubtle: true, color: statusColor, spacing: "None" }] : []),
263
+ ],
264
+ },
265
+ ],
266
+ });
267
+ // High verbosity: show input/output
268
+ if (verbosity === "high") {
269
+ const maxLen = 400;
270
+ if (tool.rawInput) {
271
+ const inputStr = typeof tool.rawInput === "string" ? tool.rawInput : JSON.stringify(tool.rawInput, null, 2);
272
+ if (inputStr && inputStr !== "{}") {
273
+ body.push({ type: "TextBlock", text: "**Input:**", size: "Small", spacing: "Medium" });
274
+ body.push({ type: "TextBlock", text: `\`\`\`\n${truncateContent(inputStr, maxLen)}\n\`\`\``, wrap: true, fontType: "Monospace", size: "Small" });
275
+ }
276
+ }
277
+ const contentText = stripCodeFences(extractContentText(tool.content));
278
+ if (contentText) {
279
+ body.push({ type: "TextBlock", text: "**Output:**", size: "Small", spacing: "Medium" });
280
+ body.push({ type: "TextBlock", text: `\`\`\`\n${truncateContent(contentText, maxLen)}\n\`\`\``, wrap: true, fontType: "Monospace", size: "Small" });
281
+ }
282
+ }
283
+ // Actions: viewer links
284
+ const actions = [];
285
+ if (tool.viewerLinks) {
286
+ const fn = tool.viewerFilePath?.split("/").pop() || "file";
287
+ if (tool.viewerLinks.file) {
288
+ actions.push({ type: "Action.OpenUrl", title: `View ${fn}`, url: tool.viewerLinks.file });
289
+ }
290
+ if (tool.viewerLinks.diff) {
291
+ actions.push({ type: "Action.OpenUrl", title: `View diff`, url: tool.viewerLinks.diff });
292
+ }
293
+ }
294
+ return { body, ...(actions.length > 0 ? { actions } : {}) };
295
+ }
296
+ /** Structured plan card with status-colored rows */
297
+ export function renderPlanCard(entries, verbosity) {
298
+ const done = entries.filter((e) => e.status === "completed").length;
299
+ const total = entries.length;
300
+ const body = [];
301
+ // Header with progress
302
+ body.push({
303
+ type: "ColumnSet",
304
+ columns: [
305
+ { type: "Column", width: "stretch", items: [{ type: "TextBlock", text: "📋 **Plan**", weight: "Bolder" }] },
306
+ { type: "Column", width: "auto", items: [{ type: "TextBlock", text: `${done}/${total}`, isSubtle: true }] },
307
+ ],
308
+ });
309
+ if (verbosity === "medium" && total > 5) {
310
+ // Compact summary for medium verbosity with many steps
311
+ body.push({ type: "TextBlock", text: `${done} of ${total} steps completed`, isSubtle: true });
312
+ return { body };
313
+ }
314
+ // Step rows with status colors
315
+ const statusColors = {
316
+ completed: "Good", in_progress: "Accent", pending: "Default",
317
+ };
318
+ const statusIcons = {
319
+ completed: "✅", in_progress: "🔄", pending: "⬜",
320
+ };
321
+ for (let i = 0; i < entries.length; i++) {
322
+ const e = entries[i];
323
+ const icon = statusIcons[e.status] || "⬜";
324
+ const color = statusColors[e.status] || "Default";
325
+ body.push({
326
+ type: "ColumnSet",
327
+ spacing: "Small",
328
+ columns: [
329
+ { type: "Column", width: "auto", items: [{ type: "TextBlock", text: icon }] },
330
+ { type: "Column", width: "stretch", items: [{ type: "TextBlock", text: `${i + 1}. ${e.content}`, wrap: true, color }] },
331
+ ],
332
+ });
333
+ }
334
+ return { body };
335
+ }
336
+ /**
337
+ * Build Teams citation entities from file references.
338
+ *
339
+ * Teams displays citations as numbered in-text references [1] with hover popups
340
+ * showing the source name, description, and link. Max 20 per message.
341
+ *
342
+ * @see https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bot-messages-ai-generated-content
343
+ */
344
+ export function buildCitationEntities(sources) {
345
+ if (sources.length === 0)
346
+ return [];
347
+ return [{
348
+ type: "https://schema.org/Message",
349
+ "@type": "Message",
350
+ "@context": "https://schema.org",
351
+ additionalType: ["AIGeneratedContent"],
352
+ citation: sources.slice(0, 20).map((source, i) => ({
353
+ "@type": "Claim",
354
+ position: i + 1,
355
+ appearance: {
356
+ "@type": "DigitalDocument",
357
+ name: source.name.slice(0, 80),
358
+ url: source.url,
359
+ ...(source.abstract ? { abstract: source.abstract.slice(0, 160) } : {}),
360
+ image: { "@type": "ImageObject", name: source.iconType ?? guessIconType(source.name) },
361
+ },
362
+ })),
363
+ }];
364
+ }
365
+ /**
366
+ * Build citation text references like "[1]" to insert into message text.
367
+ * Returns a suffix string to append to the message.
368
+ */
369
+ export function buildCitationSuffix(sources) {
370
+ if (sources.length === 0)
371
+ return "";
372
+ return "\n\n" + sources.slice(0, 20).map((s, i) => `[${i + 1}] ${s.name}`).join("\n");
373
+ }
374
+ /** Guess the Teams citation icon type from a file extension */
375
+ function guessIconType(name) {
376
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
377
+ const codeExts = new Set(["ts", "js", "tsx", "jsx", "py", "rs", "go", "java", "c", "cpp", "h", "cs", "rb", "swift", "kt", "sh", "yaml", "yml", "json", "toml", "xml", "html", "css", "scss", "sql", "md"]);
378
+ if (codeExts.has(ext))
379
+ return "Source Code";
380
+ if (ext === "pdf")
381
+ return "PDF";
382
+ if (["png", "jpg", "jpeg", "gif", "svg", "webp"].includes(ext))
383
+ return "Image";
384
+ if (["doc", "docx"].includes(ext))
385
+ return "Microsoft Word";
386
+ if (["xls", "xlsx"].includes(ext))
387
+ return "Microsoft Excel";
388
+ if (["ppt", "pptx"].includes(ext))
389
+ return "Microsoft PowerPoint";
390
+ return "Source Code";
391
+ }
392
+ //# sourceMappingURL=formatting.js.map