@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.
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/activity.d.ts +23 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/activity.js +3 -0
- package/dist/activity.js.map +1 -0
- package/dist/adapter.d.ts +174 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +1583 -0
- package/dist/adapter.js.map +1 -0
- package/dist/app-package.d.ts +7 -0
- package/dist/app-package.d.ts.map +1 -0
- package/dist/app-package.js +158 -0
- package/dist/app-package.js.map +1 -0
- package/dist/assistant.d.ts +7 -0
- package/dist/assistant.d.ts.map +1 -0
- package/dist/assistant.js +32 -0
- package/dist/assistant.js.map +1 -0
- package/dist/commands/admin.d.ts +27 -0
- package/dist/commands/admin.d.ts.map +1 -0
- package/dist/commands/admin.js +146 -0
- package/dist/commands/admin.js.map +1 -0
- package/dist/commands/agents.d.ts +13 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +98 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +49 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/index.d.ts +16 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +253 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/integrate.d.ts +8 -0
- package/dist/commands/integrate.d.ts.map +1 -0
- package/dist/commands/integrate.js +45 -0
- package/dist/commands/integrate.js.map +1 -0
- package/dist/commands/menu.d.ts +16 -0
- package/dist/commands/menu.d.ts.map +1 -0
- package/dist/commands/menu.js +92 -0
- package/dist/commands/menu.js.map +1 -0
- package/dist/commands/new-session.d.ts +13 -0
- package/dist/commands/new-session.d.ts.map +1 -0
- package/dist/commands/new-session.js +105 -0
- package/dist/commands/new-session.js.map +1 -0
- package/dist/commands/session.d.ts +22 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +110 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/settings.d.ts +8 -0
- package/dist/commands/settings.d.ts.map +1 -0
- package/dist/commands/settings.js +54 -0
- package/dist/commands/settings.js.map +1 -0
- package/dist/conversation-store.d.ts +38 -0
- package/dist/conversation-store.d.ts.map +1 -0
- package/dist/conversation-store.js +101 -0
- package/dist/conversation-store.js.map +1 -0
- package/dist/draft-manager.d.ts +47 -0
- package/dist/draft-manager.d.ts.map +1 -0
- package/dist/draft-manager.js +136 -0
- package/dist/draft-manager.js.map +1 -0
- package/dist/formatting.d.ts +121 -0
- package/dist/formatting.d.ts.map +1 -0
- package/dist/formatting.js +392 -0
- package/dist/formatting.js.map +1 -0
- package/dist/graph.d.ts +59 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +261 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/media.d.ts +29 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +120 -0
- package/dist/media.js.map +1 -0
- package/dist/permissions.d.ts +15 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +221 -0
- package/dist/permissions.js.map +1 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +689 -0
- package/dist/plugin.js.map +1 -0
- package/dist/renderer.d.ts +49 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +55 -0
- package/dist/renderer.js.map +1 -0
- package/dist/send-utils.d.ts +15 -0
- package/dist/send-utils.d.ts.map +1 -0
- package/dist/send-utils.js +64 -0
- package/dist/send-utils.js.map +1 -0
- package/dist/task-modules.d.ts +34 -0
- package/dist/task-modules.d.ts.map +1 -0
- package/dist/task-modules.js +136 -0
- package/dist/task-modules.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validators.d.ts +54 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +142 -0
- package/dist/validators.js.map +1 -0
- 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
|