@dataverse-kit/agent-kit 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,3068 @@
1
+ // src/conversation/MessageList.tsx
2
+ import * as React10 from "react";
3
+ import { Button as Button3, makeStyles as makeStyles10, tokens as tokens9 } from "@fluentui/react-components";
4
+ import { ArrowDownRegular } from "@fluentui/react-icons";
5
+
6
+ // src/core/runStatus.ts
7
+ var TERMINAL = /* @__PURE__ */ new Set([
8
+ "succeeded",
9
+ "blocked",
10
+ "refused",
11
+ "error",
12
+ "notProvisioned",
13
+ "cancelled"
14
+ ]);
15
+ function isTerminalStatus(status) {
16
+ return TERMINAL.has(status);
17
+ }
18
+ function runStatusSeverity(status) {
19
+ switch (status) {
20
+ case "succeeded":
21
+ return "success";
22
+ case "blocked":
23
+ case "refused":
24
+ return "warning";
25
+ case "error":
26
+ return "error";
27
+ case "notProvisioned":
28
+ case "cancelled":
29
+ return "info";
30
+ }
31
+ }
32
+ function classifyTransportFailure(input) {
33
+ const { httpStatus, code, retryAfterMs } = input;
34
+ const message = input.message ?? "The agent could not be reached.";
35
+ if (httpStatus === 503) {
36
+ return {
37
+ status: "notProvisioned",
38
+ message,
39
+ code: code ?? "not_provisioned",
40
+ recoverable: false,
41
+ correlationId: void 0
42
+ };
43
+ }
44
+ if (httpStatus === 429) {
45
+ return { status: "error", message, code: code ?? "rate_limited", recoverable: true, retryAfterMs };
46
+ }
47
+ if (code === "content_filter") {
48
+ return { status: "blocked", message, code, recoverable: false };
49
+ }
50
+ return { status: "error", message, code, recoverable: httpStatus !== void 0 && httpStatus >= 500 };
51
+ }
52
+
53
+ // src/core/grouping.ts
54
+ var DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1e3;
55
+ function dayKey(epochMs, timeZone) {
56
+ return new Intl.DateTimeFormat("en-CA", {
57
+ timeZone,
58
+ year: "numeric",
59
+ month: "2-digit",
60
+ day: "2-digit"
61
+ }).format(new Date(epochMs));
62
+ }
63
+ function isSameAuthorRun(a, b, windowMs = DEFAULT_GROUP_WINDOW_MS, timeZone) {
64
+ if (a.authorId !== b.authorId) return false;
65
+ if (a.role !== b.role) return false;
66
+ if (a.role === "system" || b.role === "system") return false;
67
+ if (b.createdAt < a.createdAt) return false;
68
+ if (b.createdAt - a.createdAt > windowMs) return false;
69
+ return dayKey(a.createdAt, timeZone) === dayKey(b.createdAt, timeZone);
70
+ }
71
+ function groupMessages(messages, options = {}) {
72
+ const windowMs = options.windowMs ?? DEFAULT_GROUP_WINDOW_MS;
73
+ const groups = [];
74
+ for (const message of messages) {
75
+ const current = groups[groups.length - 1];
76
+ const previousId = current?.messageIds[current.messageIds.length - 1];
77
+ const previous = previousId ? messages.find((m) => m.id === previousId) : void 0;
78
+ if (current && previous && isSameAuthorRun(previous, message, windowMs, options.timeZone)) {
79
+ current.messageIds.push(message.id);
80
+ continue;
81
+ }
82
+ groups.push({
83
+ key: `${message.authorId}:${message.id}`,
84
+ authorId: message.authorId,
85
+ role: message.role,
86
+ startedAt: message.createdAt,
87
+ messageIds: [message.id]
88
+ });
89
+ }
90
+ return groups;
91
+ }
92
+
93
+ // src/core/dayDividers.ts
94
+ function dayKeyOf(epochMs, timeZone) {
95
+ return new Intl.DateTimeFormat("en-CA", {
96
+ timeZone,
97
+ year: "numeric",
98
+ month: "2-digit",
99
+ day: "2-digit"
100
+ }).format(new Date(epochMs));
101
+ }
102
+ var DAY_MS = 24 * 60 * 60 * 1e3;
103
+ function labelFor(dayKey2, nowMs, timeZone) {
104
+ const todayKey = dayKeyOf(nowMs, timeZone);
105
+ if (dayKey2 === todayKey) return { kind: "today" };
106
+ if (dayKey2 === dayKeyOf(nowMs - DAY_MS, timeZone)) return { kind: "yesterday" };
107
+ const asDate = /* @__PURE__ */ new Date(`${dayKey2}T00:00:00Z`);
108
+ for (let back = 2; back <= 6; back++) {
109
+ if (dayKey2 === dayKeyOf(nowMs - back * DAY_MS, timeZone)) {
110
+ return { kind: "weekday", weekday: asDate.getUTCDay(), iso: dayKey2 };
111
+ }
112
+ }
113
+ return { kind: "date", iso: dayKey2 };
114
+ }
115
+ function bucketByDay(messages, options = {}) {
116
+ const nowMs = options.now ?? Date.now();
117
+ const buckets = [];
118
+ for (const message of messages) {
119
+ const key = dayKeyOf(message.createdAt, options.timeZone);
120
+ const current = buckets[buckets.length - 1];
121
+ if (current && current.dayKey === key) {
122
+ current.messageIds.push(message.id);
123
+ continue;
124
+ }
125
+ buckets.push({
126
+ dayKey: key,
127
+ label: labelFor(key, nowMs, options.timeZone),
128
+ messageIds: [message.id]
129
+ });
130
+ }
131
+ return buckets;
132
+ }
133
+
134
+ // src/core/citations.ts
135
+ var BRACKET_MARKER = /\[(\d{1,3})\]/g;
136
+ var AOAI_MARKER = /【(\d{1,3}):\d+†[^】]*】/g;
137
+ function extractCitationMarkers(text) {
138
+ const found = [];
139
+ for (const re of [BRACKET_MARKER, AOAI_MARKER]) {
140
+ re.lastIndex = 0;
141
+ let m;
142
+ while ((m = re.exec(text)) !== null) {
143
+ found.push({ marker: m[1], index: m.index, length: m[0].length });
144
+ }
145
+ }
146
+ return found.sort((a, b) => a.index - b.index);
147
+ }
148
+ function renumberCitations(text, sources) {
149
+ const markers = extractCitationMarkers(text);
150
+ const order = [];
151
+ for (const { marker } of markers) if (!order.includes(marker)) order.push(marker);
152
+ const byOriginal = /* @__PURE__ */ new Map();
153
+ order.forEach((marker, i) => byOriginal.set(marker, i + 1));
154
+ const used = [];
155
+ const citations = [];
156
+ for (const marker of order) {
157
+ const source = sources[Number(marker) - 1];
158
+ if (!source) continue;
159
+ used.push(source);
160
+ citations.push({ marker: String(byOriginal.get(marker)), sourceId: source.id });
161
+ }
162
+ let out = "";
163
+ let cursor = 0;
164
+ for (const { marker, index, length } of markers) {
165
+ out += text.slice(cursor, index);
166
+ const renumbered = byOriginal.get(marker);
167
+ out += renumbered === void 0 ? "" : `[${renumbered}]`;
168
+ cursor = index + length;
169
+ }
170
+ out += text.slice(cursor);
171
+ return { text: out, used, citations };
172
+ }
173
+ function splitTextByCitations(text, citations) {
174
+ const markers = extractCitationMarkers(text);
175
+ if (markers.length === 0) return text ? [{ kind: "text", text }] : [];
176
+ const segments = [];
177
+ let cursor = 0;
178
+ for (const { marker, index, length } of markers) {
179
+ if (index > cursor) segments.push({ kind: "text", text: text.slice(cursor, index) });
180
+ const citation = citations.find((c) => c.marker === marker);
181
+ segments.push({
182
+ kind: "citation",
183
+ text: `[${marker}]`,
184
+ marker,
185
+ sourceId: citation?.sourceId
186
+ });
187
+ cursor = index + length;
188
+ }
189
+ if (cursor < text.length) segments.push({ kind: "text", text: text.slice(cursor) });
190
+ return segments;
191
+ }
192
+
193
+ // src/core/conversationReducer.ts
194
+ function initialConversation(seed = []) {
195
+ return { messages: [...seed], streamingMessageId: null, status: "idle" };
196
+ }
197
+ function patch(state, messageId, update) {
198
+ const i = state.messages.findIndex((m) => m.id === messageId);
199
+ if (i === -1) return state.messages;
200
+ const next = state.messages.slice();
201
+ next[i] = update(next[i]);
202
+ return next;
203
+ }
204
+ function appendUserMessage(state, input) {
205
+ const message = {
206
+ id: input.id,
207
+ role: "user",
208
+ authorId: input.authorId ?? "user",
209
+ text: input.text,
210
+ createdAt: input.now ?? Date.now(),
211
+ attachments: input.attachments
212
+ };
213
+ return { ...state, messages: [...state.messages, message], status: "connecting", failure: void 0 };
214
+ }
215
+ function applyAgentEvent(state, event) {
216
+ switch (event.type) {
217
+ case "turn_start": {
218
+ const message = {
219
+ id: event.messageId,
220
+ role: "assistant",
221
+ authorId: event.provider ?? "agent",
222
+ text: "",
223
+ createdAt: Date.now(),
224
+ streaming: true,
225
+ provider: event.provider
226
+ };
227
+ return {
228
+ ...state,
229
+ messages: [...state.messages, message],
230
+ streamingMessageId: event.messageId,
231
+ status: "streaming",
232
+ statusLabel: void 0,
233
+ failure: void 0,
234
+ suggestions: void 0
235
+ };
236
+ }
237
+ case "text_delta":
238
+ return {
239
+ ...state,
240
+ messages: patch(state, event.messageId, (m) => ({ ...m, text: m.text + event.text }))
241
+ };
242
+ case "thinking_delta":
243
+ return {
244
+ ...state,
245
+ messages: patch(state, event.messageId, (m) => ({
246
+ ...m,
247
+ thinking: (m.thinking ?? "") + event.text
248
+ }))
249
+ };
250
+ case "tool_call":
251
+ return {
252
+ ...state,
253
+ messages: patch(state, event.messageId, (m) => ({
254
+ ...m,
255
+ toolCalls: [...m.toolCalls ?? [], event.call]
256
+ }))
257
+ };
258
+ case "tool_result":
259
+ return {
260
+ ...state,
261
+ messages: patch(state, event.messageId, (m) => ({
262
+ ...m,
263
+ toolCalls: (m.toolCalls ?? []).map(
264
+ (c) => c.id === event.callId ? { ...c, status: event.status, resultText: event.resultText, failure: event.failure } : c
265
+ )
266
+ }))
267
+ };
268
+ case "sources":
269
+ return {
270
+ ...state,
271
+ messages: patch(state, event.messageId, (m) => ({ ...m, sources: event.sources }))
272
+ };
273
+ case "citation":
274
+ return {
275
+ ...state,
276
+ messages: patch(state, event.messageId, (m) => ({
277
+ ...m,
278
+ citations: [...m.citations ?? [], event.citation]
279
+ }))
280
+ };
281
+ case "suggestions":
282
+ return { ...state, suggestions: event.prompts };
283
+ case "status":
284
+ return { ...state, statusLabel: event.label };
285
+ case "turn_end": {
286
+ const { result } = event;
287
+ return {
288
+ ...state,
289
+ messages: patch(state, event.messageId, (m) => ({
290
+ ...m,
291
+ // A non-streaming provider sends no deltas, so the assembled text arrives here.
292
+ text: m.text.length > 0 ? m.text : result.text,
293
+ streaming: false,
294
+ guarded: result.guarded,
295
+ provider: result.provider,
296
+ usage: result.usage,
297
+ failure: result.failure
298
+ })),
299
+ streamingMessageId: null,
300
+ status: result.status,
301
+ statusLabel: void 0,
302
+ failure: result.failure,
303
+ usage: result.usage
304
+ };
305
+ }
306
+ }
307
+ }
308
+
309
+ // src/core/normalize.ts
310
+ function fromSseLine(line) {
311
+ const trimmed = line.trim();
312
+ if (trimmed === "" || trimmed.startsWith(":")) return void 0;
313
+ if (!trimmed.startsWith("data:")) return void 0;
314
+ const payload = trimmed.slice("data:".length).trim();
315
+ if (payload === "") return void 0;
316
+ if (payload === "[DONE]") return "[DONE]";
317
+ try {
318
+ return JSON.parse(payload);
319
+ } catch {
320
+ return void 0;
321
+ }
322
+ }
323
+ function fromOpenAiChatChunk(chunk, ctx) {
324
+ const events = [];
325
+ const delta = chunk?.choices?.[0]?.delta;
326
+ if (!delta) return events;
327
+ if (typeof delta.content === "string" && delta.content.length > 0) {
328
+ events.push({ type: "text_delta", messageId: ctx.messageId, text: delta.content });
329
+ }
330
+ for (const call of delta.tool_calls ?? []) {
331
+ if (!call.id) continue;
332
+ const toolCall = {
333
+ id: call.id,
334
+ name: call.function?.name ?? "",
335
+ argumentsJson: call.function?.arguments ?? "",
336
+ status: "proposed"
337
+ };
338
+ events.push({ type: "tool_call", messageId: ctx.messageId, call: toolCall });
339
+ }
340
+ return events;
341
+ }
342
+ function fromClaudeStreamEvent(event, ctx) {
343
+ switch (event.type) {
344
+ case "init":
345
+ return [
346
+ {
347
+ type: "turn_start",
348
+ turnId: ctx.turnId,
349
+ messageId: ctx.messageId,
350
+ model: event.model,
351
+ provider: "claude-bridge",
352
+ sessionId: event.sessionId
353
+ }
354
+ ];
355
+ case "text_delta":
356
+ return event.text ? [{ type: "text_delta", messageId: ctx.messageId, text: event.text }] : [];
357
+ case "thinking_delta":
358
+ return event.text ? [{ type: "thinking_delta", messageId: ctx.messageId, text: event.text }] : [];
359
+ case "done":
360
+ return [
361
+ {
362
+ type: "turn_end",
363
+ turnId: ctx.turnId,
364
+ messageId: ctx.messageId,
365
+ result: {
366
+ status: "succeeded",
367
+ text: event.fullText ?? "",
368
+ provider: "claude-bridge",
369
+ model: event.model,
370
+ usage: {
371
+ inputTokens: event.inputTokens,
372
+ outputTokens: event.outputTokens,
373
+ cachedTokens: event.cacheReadTokens,
374
+ costUsd: event.costUsd,
375
+ durationMs: event.durationMs,
376
+ model: event.model
377
+ }
378
+ }
379
+ }
380
+ ];
381
+ case "error":
382
+ return [
383
+ {
384
+ type: "turn_end",
385
+ turnId: ctx.turnId,
386
+ messageId: ctx.messageId,
387
+ result: {
388
+ status: "error",
389
+ text: "",
390
+ provider: "claude-bridge",
391
+ failure: {
392
+ status: "error",
393
+ message: event.message ?? "The agent failed.",
394
+ recoverable: event.recoverable ?? false
395
+ }
396
+ }
397
+ }
398
+ ];
399
+ }
400
+ }
401
+ function fromFoundryChatResult(result, ctx, provider = "foundry") {
402
+ return [
403
+ { type: "turn_start", turnId: ctx.turnId, messageId: ctx.messageId, model: result.model, provider },
404
+ {
405
+ type: "turn_end",
406
+ turnId: ctx.turnId,
407
+ messageId: ctx.messageId,
408
+ result: {
409
+ status: "succeeded",
410
+ text: result.text,
411
+ provider,
412
+ model: result.model,
413
+ usage: {
414
+ inputTokens: result.promptTokens,
415
+ outputTokens: result.completionTokens,
416
+ model: result.model
417
+ }
418
+ }
419
+ }
420
+ ];
421
+ }
422
+
423
+ // src/core/announce.ts
424
+ var ANNOUNCE_FULL_TEXT_MAX_CHARS = 240;
425
+ var NO_ANNOUNCEMENT = { polite: "", assertive: "" };
426
+ var TERMINAL2 = /* @__PURE__ */ new Set(["succeeded", "blocked", "refused", "error", "notProvisioned", "cancelled"]);
427
+ function deriveAnnouncement(state, previous) {
428
+ const wasTerminal = previous ? TERMINAL2.has(previous.status) : false;
429
+ const isTerminal = TERMINAL2.has(state.status);
430
+ if (!isTerminal || wasTerminal) return NO_ANNOUNCEMENT;
431
+ if (state.failure) {
432
+ return { polite: "", assertive: state.failure.message };
433
+ }
434
+ const last = state.messages[state.messages.length - 1];
435
+ const text = last?.text ?? "";
436
+ if (!text) return NO_ANNOUNCEMENT;
437
+ const guarded = last?.guarded ? "The response was withheld. " : "";
438
+ if (text.length <= ANNOUNCE_FULL_TEXT_MAX_CHARS) {
439
+ return { polite: `${guarded}${text}`, assertive: "" };
440
+ }
441
+ const words = text.trim().split(/\s+/).length;
442
+ return {
443
+ polite: `${guarded}Response complete, ${words} words.`,
444
+ assertive: ""
445
+ };
446
+ }
447
+
448
+ // src/core/scroll.ts
449
+ var STICK_TO_BOTTOM_THRESHOLD_PX = 32;
450
+ function shouldStickToBottom(geometry, thresholdPx = STICK_TO_BOTTOM_THRESHOLD_PX) {
451
+ const { scrollTop, scrollHeight, clientHeight } = geometry;
452
+ const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
453
+ return distanceFromBottom <= thresholdPx;
454
+ }
455
+
456
+ // src/core/ids.ts
457
+ function createIdPrefixCounter(tag) {
458
+ let n = 0;
459
+ return () => `${tag}${++n}-`;
460
+ }
461
+
462
+ // src/a11y/LiveAnnouncer.tsx
463
+ import * as React from "react";
464
+
465
+ // src/a11y/visuallyHidden.ts
466
+ import { makeStyles } from "@fluentui/react-components";
467
+ var useVisuallyHiddenStyles = makeStyles({
468
+ root: {
469
+ position: "absolute",
470
+ width: "1px",
471
+ height: "1px",
472
+ margin: "-1px",
473
+ padding: "0",
474
+ overflow: "hidden",
475
+ clip: "rect(0 0 0 0)",
476
+ clipPath: "inset(50%)",
477
+ whiteSpace: "nowrap"
478
+ }
479
+ });
480
+
481
+ // src/a11y/LiveAnnouncer.tsx
482
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
483
+ function useAnnounceWithRepeat(next, set) {
484
+ React.useEffect(() => {
485
+ if (!next) return;
486
+ let cancelled = false;
487
+ set("");
488
+ const id = setTimeout(() => {
489
+ if (!cancelled) set(next);
490
+ }, 0);
491
+ return () => {
492
+ cancelled = true;
493
+ clearTimeout(id);
494
+ };
495
+ }, [next, set]);
496
+ }
497
+ function LiveAnnouncer({
498
+ polite = "",
499
+ assertive = ""
500
+ }) {
501
+ const styles = useVisuallyHiddenStyles();
502
+ const [politeText, setPoliteText] = React.useState("");
503
+ const [assertiveText, setAssertiveText] = React.useState("");
504
+ useAnnounceWithRepeat(polite, setPoliteText);
505
+ useAnnounceWithRepeat(assertive, setAssertiveText);
506
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
507
+ /* @__PURE__ */ jsx(
508
+ "div",
509
+ {
510
+ className: styles.root,
511
+ "aria-live": "polite",
512
+ "aria-atomic": "true",
513
+ "data-testid": "agentkit-live-polite",
514
+ children: politeText
515
+ }
516
+ ),
517
+ /* @__PURE__ */ jsx(
518
+ "div",
519
+ {
520
+ className: styles.root,
521
+ role: "alert",
522
+ "aria-live": "assertive",
523
+ "aria-atomic": "true",
524
+ "data-testid": "agentkit-live-assertive",
525
+ children: assertiveText
526
+ }
527
+ )
528
+ ] });
529
+ }
530
+
531
+ // src/a11y/useFocusRestore.ts
532
+ import * as React2 from "react";
533
+ function useFocusRestore(containerRef) {
534
+ return React2.useCallback(() => {
535
+ const container = containerRef.current;
536
+ if (!container) return;
537
+ const doc = container.ownerDocument;
538
+ if (doc.activeElement && doc.activeElement !== doc.body) return;
539
+ const focusable = container.querySelector(
540
+ 'a[href],button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])'
541
+ );
542
+ (focusable ?? container).focus();
543
+ }, [containerRef]);
544
+ }
545
+
546
+ // src/a11y/useRovingTabindex.ts
547
+ import * as React3 from "react";
548
+ function useRovingTabindex(options) {
549
+ const { orientation = "horizontal", loop = true, itemCount, onActivate } = options;
550
+ const [activeIndex, setActiveIndex] = React3.useState(0);
551
+ const clamp = React3.useCallback(
552
+ (index) => {
553
+ if (itemCount === 0) return 0;
554
+ return loop ? (index + itemCount) % itemCount : Math.min(Math.max(index, 0), itemCount - 1);
555
+ },
556
+ [itemCount, loop]
557
+ );
558
+ const getItemProps = React3.useCallback(
559
+ (index) => ({
560
+ tabIndex: index === activeIndex ? 0 : -1,
561
+ "data-roving-item": true,
562
+ onFocus: () => setActiveIndex(index),
563
+ onKeyDown: (event) => {
564
+ const prevKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
565
+ const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
566
+ let target = null;
567
+ if (event.key === nextKey) target = clamp(index + 1);
568
+ else if (event.key === prevKey) target = clamp(index - 1);
569
+ else if (event.key === "Home") target = 0;
570
+ else if (event.key === "End") target = itemCount - 1;
571
+ else if (event.key === "Enter" || event.key === " ") {
572
+ onActivate?.(index);
573
+ return;
574
+ }
575
+ if (target === null) return;
576
+ event.preventDefault();
577
+ event.stopPropagation();
578
+ setActiveIndex(target);
579
+ const group = event.currentTarget.parentElement;
580
+ group?.querySelectorAll("[data-roving-item]")[target]?.focus();
581
+ }
582
+ }),
583
+ [activeIndex, clamp, itemCount, onActivate, orientation]
584
+ );
585
+ return { activeIndex, setActiveIndex, getItemProps };
586
+ }
587
+
588
+ // src/hooks/useStickyScroll.ts
589
+ import * as React4 from "react";
590
+ function useStickyScroll(ref, messageCount) {
591
+ const wasPinnedRef = React4.useRef(true);
592
+ const [isPinned, setIsPinned] = React4.useState(true);
593
+ const onScroll = React4.useCallback(() => {
594
+ const el = ref.current;
595
+ if (!el) return;
596
+ const pinned = shouldStickToBottom({
597
+ scrollTop: el.scrollTop,
598
+ scrollHeight: el.scrollHeight,
599
+ clientHeight: el.clientHeight
600
+ });
601
+ wasPinnedRef.current = pinned;
602
+ setIsPinned(pinned);
603
+ }, [ref]);
604
+ const scrollToBottom = React4.useCallback(() => {
605
+ const el = ref.current;
606
+ if (!el) return;
607
+ el.scrollTo({ top: el.scrollHeight, behavior: "auto" });
608
+ wasPinnedRef.current = true;
609
+ setIsPinned(true);
610
+ }, [ref]);
611
+ React4.useEffect(() => {
612
+ if (!wasPinnedRef.current) return;
613
+ const el = ref.current;
614
+ if (!el) return;
615
+ el.scrollTo({ top: el.scrollHeight, behavior: "auto" });
616
+ }, [messageCount, ref]);
617
+ return { onScroll, scrollToBottom, isPinned };
618
+ }
619
+
620
+ // src/hooks/useConversation.ts
621
+ import * as React5 from "react";
622
+ var fallbackId = 0;
623
+ function useConversation(options) {
624
+ const { service, seed, grounding, conversationId, createId } = options;
625
+ const [state, setState] = React5.useState(() => initialConversation(seed));
626
+ const [announcement, setAnnouncement] = React5.useState(NO_ANNOUNCEMENT);
627
+ const abortRef = React5.useRef(null);
628
+ const stateRef = React5.useRef(state);
629
+ stateRef.current = state;
630
+ const nextId = React5.useCallback(
631
+ () => createId?.() ?? `m${++fallbackId}-${Date.now().toString(36)}`,
632
+ [createId]
633
+ );
634
+ const commit = React5.useCallback((next) => {
635
+ const previous = stateRef.current;
636
+ stateRef.current = next;
637
+ setState(next);
638
+ const derived = deriveAnnouncement(next, previous);
639
+ if (derived.polite || derived.assertive) setAnnouncement(derived);
640
+ }, []);
641
+ const cancel = React5.useCallback(() => {
642
+ abortRef.current?.abort();
643
+ }, []);
644
+ const send = React5.useCallback(
645
+ async (text, attachments) => {
646
+ if (!text.trim()) return;
647
+ abortRef.current?.abort();
648
+ const controller = new AbortController();
649
+ abortRef.current = controller;
650
+ let next = appendUserMessage(stateRef.current, { id: nextId(), text, attachments });
651
+ commit(next);
652
+ try {
653
+ for await (const event of service.send({
654
+ text,
655
+ history: next.messages,
656
+ grounding,
657
+ attachments,
658
+ conversationId,
659
+ cancelSignal: controller.signal
660
+ })) {
661
+ next = applyAgentEvent(next, event);
662
+ commit(next);
663
+ }
664
+ } catch (error) {
665
+ commit({
666
+ ...next,
667
+ streamingMessageId: null,
668
+ status: "error",
669
+ failure: {
670
+ status: "error",
671
+ message: error instanceof Error ? error.message : String(error),
672
+ recoverable: true
673
+ }
674
+ });
675
+ } finally {
676
+ if (abortRef.current === controller) abortRef.current = null;
677
+ }
678
+ },
679
+ [service, grounding, conversationId, nextId, commit]
680
+ );
681
+ React5.useEffect(() => () => abortRef.current?.abort(), []);
682
+ const busy = state.status === "connecting" || state.status === "streaming";
683
+ return { state, announcement, busy, send, cancel };
684
+ }
685
+
686
+ // src/conversation/MessageBubble.tsx
687
+ import * as React9 from "react";
688
+ import { Avatar, makeStyles as makeStyles7, mergeClasses as mergeClasses4, tokens as tokens6 } from "@fluentui/react-components";
689
+
690
+ // src/conversation/StreamingText.tsx
691
+ import { makeStyles as makeStyles2, tokens } from "@fluentui/react-components";
692
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
693
+ var useStyles = makeStyles2({
694
+ root: { whiteSpace: "pre-wrap", wordBreak: "break-word" },
695
+ caret: {
696
+ display: "inline-block",
697
+ width: "2px",
698
+ height: "1em",
699
+ marginLeft: "2px",
700
+ verticalAlign: "text-bottom",
701
+ backgroundColor: tokens.colorBrandForeground1,
702
+ animationName: { "0%,49%": { opacity: 1 }, "50%,100%": { opacity: 0 } },
703
+ animationDuration: "1s",
704
+ animationIterationCount: "infinite",
705
+ // WCAG 2.2.2: a blinking caret is motion, and some readers cannot tolerate it.
706
+ "@media (prefers-reduced-motion: reduce)": { animationName: "none", opacity: 1 }
707
+ }
708
+ });
709
+ function StreamingText({ text, streaming }) {
710
+ const styles = useStyles();
711
+ return /* @__PURE__ */ jsxs2("span", { className: styles.root, "data-testid": "agentkit-streaming-text", children: [
712
+ text,
713
+ streaming ? /* @__PURE__ */ jsx2("span", { className: styles.caret, "aria-hidden": "true" }) : null
714
+ ] });
715
+ }
716
+
717
+ // src/conversation/MessageActions.tsx
718
+ import { CopyRegular, ArrowClockwiseRegular, ThumbLikeRegular, ThumbDislikeRegular } from "@fluentui/react-icons";
719
+
720
+ // src/internal/ActionToolbar.tsx
721
+ import { Button, Tooltip, makeStyles as makeStyles3, tokens as tokens2, mergeClasses } from "@fluentui/react-components";
722
+ import { jsx as jsx3 } from "react/jsx-runtime";
723
+ var useStyles2 = makeStyles3({
724
+ root: {
725
+ display: "inline-flex",
726
+ gap: tokens2.spacingHorizontalXXS,
727
+ alignItems: "center",
728
+ backgroundColor: tokens2.colorNeutralBackground1,
729
+ borderRadius: tokens2.borderRadiusMedium,
730
+ boxShadow: tokens2.shadow4,
731
+ padding: tokens2.spacingHorizontalXXS
732
+ },
733
+ button: {
734
+ // ★ A focus ring that survives Windows forced-colors. Once the kit owns its own focus
735
+ // management it owns the indicator too, and `outline: none` + box-shadow — the usual
736
+ // custom-focus recipe — disappears entirely under forced-colors.
737
+ ":focus-visible": {
738
+ outline: `2px solid ${tokens2.colorStrokeFocus2}`,
739
+ outlineOffset: "1px"
740
+ }
741
+ }
742
+ });
743
+ function ActionToolbar({ actions, label, className }) {
744
+ const styles = useStyles2();
745
+ const roving = useRovingTabindex({ itemCount: actions.length, orientation: "horizontal" });
746
+ return /* @__PURE__ */ jsx3("div", { role: "toolbar", "aria-label": label, "aria-orientation": "horizontal", className: mergeClasses(styles.root, className), children: actions.map((action, index) => {
747
+ const item = roving.getItemProps(index);
748
+ return /* @__PURE__ */ jsx3(Tooltip, { content: action.label, relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx3(
749
+ Button,
750
+ {
751
+ appearance: "subtle",
752
+ size: "small",
753
+ icon: action.icon,
754
+ disabled: action.disabled,
755
+ onClick: action.onClick,
756
+ className: styles.button,
757
+ ...item
758
+ }
759
+ ) }, action.key);
760
+ }) });
761
+ }
762
+
763
+ // src/internal/CardSurface.tsx
764
+ import * as React6 from "react";
765
+ import { makeStyles as makeStyles4, mergeClasses as mergeClasses2, tokens as tokens3 } from "@fluentui/react-components";
766
+ import { jsx as jsx4 } from "react/jsx-runtime";
767
+ var useStyles3 = makeStyles4({
768
+ root: {
769
+ display: "flex",
770
+ flexDirection: "column",
771
+ gap: tokens3.spacingVerticalXS,
772
+ padding: tokens3.spacingVerticalM,
773
+ borderRadius: tokens3.borderRadiusMedium,
774
+ backgroundColor: tokens3.colorNeutralBackground1,
775
+ border: `1px solid ${tokens3.colorNeutralStroke2}`,
776
+ boxShadow: tokens3.shadow2
777
+ },
778
+ attention: { borderLeft: `3px solid ${tokens3.colorBrandStroke1}` }
779
+ });
780
+ var CardSurface = React6.forwardRef(
781
+ function CardSurface2({ emphasis = "none", className, children, ...rest }, ref) {
782
+ const styles = useStyles3();
783
+ return /* @__PURE__ */ jsx4(
784
+ "div",
785
+ {
786
+ ref,
787
+ className: mergeClasses2(styles.root, emphasis === "attention" && styles.attention, className),
788
+ ...rest,
789
+ children
790
+ }
791
+ );
792
+ }
793
+ );
794
+
795
+ // src/internal/ConfirmInline.tsx
796
+ import * as React7 from "react";
797
+ import { Button as Button2, makeStyles as makeStyles5, tokens as tokens4, Text } from "@fluentui/react-components";
798
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
799
+ var useStyles4 = makeStyles5({
800
+ root: { maxWidth: "420px" },
801
+ actions: { display: "flex", gap: tokens4.spacingHorizontalS, justifyContent: "flex-end", marginTop: tokens4.spacingVerticalS }
802
+ });
803
+ var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
804
+ function ConfirmInline(props) {
805
+ const { open, title, body, confirmLabel = "Confirm", cancelLabel = "Cancel", destructive, onConfirm, onCancel } = props;
806
+ const styles = useStyles4();
807
+ const ref = React7.useRef(null);
808
+ const returnTo = React7.useRef(null);
809
+ const titleId = React7.useId();
810
+ const bodyId = React7.useId();
811
+ React7.useEffect(() => {
812
+ if (!open) return;
813
+ returnTo.current = document.activeElement ?? null;
814
+ const first = ref.current?.querySelector(FOCUSABLE);
815
+ first?.focus();
816
+ return () => {
817
+ const target = returnTo.current;
818
+ if (target && target.isConnected) target.focus();
819
+ };
820
+ }, [open]);
821
+ if (!open) return null;
822
+ const onKeyDown = (event) => {
823
+ if (event.key === "Escape") {
824
+ event.stopPropagation();
825
+ onCancel();
826
+ return;
827
+ }
828
+ if (event.key !== "Tab") return;
829
+ const items = [...ref.current?.querySelectorAll(FOCUSABLE) ?? []];
830
+ if (items.length === 0) return;
831
+ const first = items[0];
832
+ const last = items[items.length - 1];
833
+ const active = document.activeElement;
834
+ if (event.shiftKey && active === first) {
835
+ event.preventDefault();
836
+ last.focus();
837
+ } else if (!event.shiftKey && active === last) {
838
+ event.preventDefault();
839
+ first.focus();
840
+ }
841
+ };
842
+ return /* @__PURE__ */ jsxs3(
843
+ CardSurface,
844
+ {
845
+ ref,
846
+ role: "alertdialog",
847
+ "aria-modal": "true",
848
+ "aria-labelledby": titleId,
849
+ "aria-describedby": bodyId,
850
+ onKeyDown,
851
+ className: styles.root,
852
+ emphasis: "attention",
853
+ children: [
854
+ /* @__PURE__ */ jsx5(Text, { id: titleId, weight: "semibold", children: title }),
855
+ /* @__PURE__ */ jsx5(Text, { id: bodyId, size: 200, children: body }),
856
+ /* @__PURE__ */ jsxs3("div", { className: styles.actions, children: [
857
+ /* @__PURE__ */ jsx5(Button2, { size: "small", onClick: onCancel, children: cancelLabel }),
858
+ /* @__PURE__ */ jsx5(Button2, { size: "small", appearance: destructive ? "primary" : "primary", onClick: onConfirm, children: confirmLabel })
859
+ ] })
860
+ ]
861
+ }
862
+ );
863
+ }
864
+
865
+ // src/internal/Disclosure.tsx
866
+ import * as React8 from "react";
867
+ import { makeStyles as makeStyles6, tokens as tokens5, mergeClasses as mergeClasses3 } from "@fluentui/react-components";
868
+ import { ChevronRightRegular, ChevronDownRegular } from "@fluentui/react-icons";
869
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
870
+ var useStyles5 = makeStyles6({
871
+ trigger: {
872
+ display: "flex",
873
+ alignItems: "center",
874
+ gap: tokens5.spacingHorizontalXS,
875
+ background: "none",
876
+ border: "none",
877
+ cursor: "pointer",
878
+ padding: `${tokens5.spacingVerticalXXS} 0`,
879
+ color: tokens5.colorNeutralForeground2,
880
+ font: "inherit",
881
+ fontSize: tokens5.fontSizeBase200,
882
+ ":focus-visible": { outline: `2px solid ${tokens5.colorStrokeFocus2}`, outlineOffset: "1px" }
883
+ },
884
+ panel: { paddingTop: tokens5.spacingVerticalXS }
885
+ });
886
+ function Disclosure({
887
+ id,
888
+ summary,
889
+ children,
890
+ defaultOpen = false,
891
+ className
892
+ }) {
893
+ const styles = useStyles5();
894
+ const [open, setOpen] = React8.useState(defaultOpen);
895
+ const panelId = `${id}-panel`;
896
+ return /* @__PURE__ */ jsxs4("div", { className, children: [
897
+ /* @__PURE__ */ jsxs4(
898
+ "button",
899
+ {
900
+ type: "button",
901
+ id,
902
+ className: styles.trigger,
903
+ "aria-expanded": open,
904
+ "aria-controls": panelId,
905
+ onClick: () => setOpen((v) => !v),
906
+ children: [
907
+ open ? /* @__PURE__ */ jsx6(ChevronDownRegular, {}) : /* @__PURE__ */ jsx6(ChevronRightRegular, {}),
908
+ summary
909
+ ]
910
+ }
911
+ ),
912
+ /* @__PURE__ */ jsx6(
913
+ "div",
914
+ {
915
+ id: panelId,
916
+ role: "region",
917
+ "aria-labelledby": id,
918
+ hidden: !open,
919
+ className: mergeClasses3(styles.panel),
920
+ children: open ? children : null
921
+ }
922
+ )
923
+ ] });
924
+ }
925
+
926
+ // src/conversation/MessageActions.tsx
927
+ import { jsx as jsx7 } from "react/jsx-runtime";
928
+ function MessageActions({
929
+ message,
930
+ onCopy,
931
+ onRetry,
932
+ onFeedback
933
+ }) {
934
+ const actions = [];
935
+ if (onCopy) {
936
+ actions.push({ key: "copy", label: "Copy message", icon: /* @__PURE__ */ jsx7(CopyRegular, {}), onClick: () => onCopy(message) });
937
+ }
938
+ if (onRetry && message.role === "assistant") {
939
+ actions.push({ key: "retry", label: "Regenerate", icon: /* @__PURE__ */ jsx7(ArrowClockwiseRegular, {}), onClick: () => onRetry(message) });
940
+ }
941
+ if (onFeedback && message.role === "assistant") {
942
+ actions.push(
943
+ { key: "up", label: "Good response", icon: /* @__PURE__ */ jsx7(ThumbLikeRegular, {}), onClick: () => onFeedback(message, "up") },
944
+ { key: "down", label: "Bad response", icon: /* @__PURE__ */ jsx7(ThumbDislikeRegular, {}), onClick: () => onFeedback(message, "down") }
945
+ );
946
+ }
947
+ if (actions.length === 0) return null;
948
+ return /* @__PURE__ */ jsx7(ActionToolbar, { actions, label: `Actions for this message` });
949
+ }
950
+
951
+ // src/conversation/MessageBubble.tsx
952
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
953
+ var useStyles6 = makeStyles7({
954
+ row: {
955
+ display: "flex",
956
+ gap: tokens6.spacingHorizontalS,
957
+ position: "relative",
958
+ paddingRight: tokens6.spacingHorizontalXXL
959
+ },
960
+ own: { flexDirection: "row-reverse", paddingRight: 0, paddingLeft: tokens6.spacingHorizontalXXL },
961
+ gutter: { width: "32px", flexShrink: 0 },
962
+ stack: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0, maxWidth: "80%" },
963
+ name: { fontSize: tokens6.fontSizeBase200, color: tokens6.colorNeutralForeground3 },
964
+ bubble: {
965
+ padding: `${tokens6.spacingVerticalS} ${tokens6.spacingHorizontalM}`,
966
+ borderRadius: tokens6.borderRadiusLarge,
967
+ backgroundColor: tokens6.colorNeutralBackground3,
968
+ color: tokens6.colorNeutralForeground1,
969
+ fontSize: tokens6.fontSizeBase300
970
+ },
971
+ ownBubble: {
972
+ backgroundColor: tokens6.colorBrandBackground,
973
+ color: tokens6.colorNeutralForegroundOnBrand
974
+ },
975
+ actions: {
976
+ position: "absolute",
977
+ top: "-12px",
978
+ right: tokens6.spacingHorizontalS,
979
+ opacity: 0,
980
+ pointerEvents: "none",
981
+ transitionProperty: "opacity",
982
+ transitionDuration: tokens6.durationFaster
983
+ },
984
+ // ★ hover OR focus-within. Without the second selector the row is unreachable by keyboard.
985
+ actionsVisible: { opacity: 1, pointerEvents: "auto" }
986
+ });
987
+ function MessageBubble(props) {
988
+ const { message, author, isRunStart = true, locale, ...actionHandlers } = props;
989
+ const styles = useStyles6();
990
+ const hidden = useVisuallyHiddenStyles();
991
+ const [revealed, setRevealed] = React9.useState(false);
992
+ const own = message.role === "user";
993
+ const name = author?.displayName ?? (own ? "You" : message.provider ?? "Assistant");
994
+ const time = new Intl.DateTimeFormat(locale, { timeStyle: "short" }).format(new Date(message.createdAt));
995
+ return /* @__PURE__ */ jsxs5(
996
+ "div",
997
+ {
998
+ className: mergeClasses4(styles.row, own && styles.own),
999
+ onMouseEnter: () => setRevealed(true),
1000
+ onMouseLeave: () => setRevealed(false),
1001
+ onFocus: () => setRevealed(true),
1002
+ onBlur: (e) => {
1003
+ if (!e.currentTarget.contains(e.relatedTarget)) setRevealed(false);
1004
+ },
1005
+ "data-testid": "agentkit-message",
1006
+ children: [
1007
+ /* @__PURE__ */ jsx8("div", { className: styles.gutter, children: isRunStart ? /* @__PURE__ */ jsx8(Avatar, { name, size: 32, image: author?.imageUrl ? { src: author.imageUrl } : void 0 }) : null }),
1008
+ /* @__PURE__ */ jsxs5("div", { className: styles.stack, children: [
1009
+ /* @__PURE__ */ jsxs5("span", { className: hidden.root, children: [
1010
+ name,
1011
+ ", ",
1012
+ /* @__PURE__ */ jsx8("time", { dateTime: new Date(message.createdAt).toISOString(), children: time })
1013
+ ] }),
1014
+ isRunStart ? /* @__PURE__ */ jsxs5("span", { className: styles.name, "aria-hidden": "true", children: [
1015
+ name,
1016
+ " \xB7 ",
1017
+ time
1018
+ ] }) : null,
1019
+ /* @__PURE__ */ jsx8(
1020
+ "div",
1021
+ {
1022
+ className: mergeClasses4(styles.bubble, own && styles.ownBubble),
1023
+ "aria-busy": message.streaming || void 0,
1024
+ children: /* @__PURE__ */ jsx8(StreamingText, { text: message.text, streaming: message.streaming })
1025
+ }
1026
+ )
1027
+ ] }),
1028
+ /* @__PURE__ */ jsx8("div", { className: mergeClasses4(styles.actions, revealed && styles.actionsVisible), children: /* @__PURE__ */ jsx8(MessageActions, { message, ...actionHandlers }) })
1029
+ ]
1030
+ }
1031
+ );
1032
+ }
1033
+
1034
+ // src/conversation/DayDivider.tsx
1035
+ import { makeStyles as makeStyles8, tokens as tokens7 } from "@fluentui/react-components";
1036
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1037
+ var useStyles7 = makeStyles8({
1038
+ root: {
1039
+ display: "flex",
1040
+ alignItems: "center",
1041
+ gap: tokens7.spacingHorizontalM,
1042
+ margin: `${tokens7.spacingVerticalM} 0`,
1043
+ color: tokens7.colorNeutralForeground3,
1044
+ fontSize: tokens7.fontSizeBase200
1045
+ },
1046
+ rule: { flex: 1, height: "1px", backgroundColor: tokens7.colorNeutralStroke2 }
1047
+ });
1048
+ function formatDayLabel(label, locale) {
1049
+ switch (label.kind) {
1050
+ case "today":
1051
+ return "Today";
1052
+ case "yesterday":
1053
+ return "Yesterday";
1054
+ case "weekday":
1055
+ return new Intl.DateTimeFormat(locale, { weekday: "long", timeZone: "UTC" }).format(
1056
+ /* @__PURE__ */ new Date(`${label.iso}T00:00:00Z`)
1057
+ );
1058
+ case "date":
1059
+ return new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeZone: "UTC" }).format(
1060
+ /* @__PURE__ */ new Date(`${label.iso}T00:00:00Z`)
1061
+ );
1062
+ }
1063
+ }
1064
+ function DayDivider({ label, locale }) {
1065
+ const styles = useStyles7();
1066
+ const text = formatDayLabel(label, locale);
1067
+ return /* @__PURE__ */ jsxs6("div", { className: styles.root, role: "separator", "aria-label": text, children: [
1068
+ /* @__PURE__ */ jsx9("span", { className: styles.rule, "aria-hidden": "true" }),
1069
+ /* @__PURE__ */ jsx9("span", { children: text }),
1070
+ /* @__PURE__ */ jsx9("span", { className: styles.rule, "aria-hidden": "true" })
1071
+ ] });
1072
+ }
1073
+
1074
+ // src/conversation/TypingIndicator.tsx
1075
+ import { makeStyles as makeStyles9, tokens as tokens8 } from "@fluentui/react-components";
1076
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1077
+ var useStyles8 = makeStyles9({
1078
+ root: { display: "inline-flex", gap: "4px", alignItems: "center", padding: tokens8.spacingVerticalXS },
1079
+ dot: {
1080
+ width: "6px",
1081
+ height: "6px",
1082
+ borderRadius: tokens8.borderRadiusCircular,
1083
+ backgroundColor: tokens8.colorNeutralForeground3,
1084
+ animationName: { "0%,80%,100%": { opacity: 0.3 }, "40%": { opacity: 1 } },
1085
+ animationDuration: "1.4s",
1086
+ animationIterationCount: "infinite",
1087
+ "@media (prefers-reduced-motion: reduce)": { animationName: "none", opacity: 0.6 }
1088
+ },
1089
+ d2: { animationDelay: "0.2s" },
1090
+ d3: { animationDelay: "0.4s" }
1091
+ });
1092
+ function TypingIndicator({ label = "Assistant is responding" }) {
1093
+ const styles = useStyles8();
1094
+ return /* @__PURE__ */ jsxs7("div", { className: styles.root, role: "status", "aria-label": label, "data-testid": "agentkit-typing", children: [
1095
+ /* @__PURE__ */ jsx10("span", { className: styles.dot, "aria-hidden": "true" }),
1096
+ /* @__PURE__ */ jsx10("span", { className: `${styles.dot} ${styles.d2}`, "aria-hidden": "true" }),
1097
+ /* @__PURE__ */ jsx10("span", { className: `${styles.dot} ${styles.d3}`, "aria-hidden": "true" })
1098
+ ] });
1099
+ }
1100
+
1101
+ // src/conversation/MessageList.tsx
1102
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1103
+ var useStyles9 = makeStyles10({
1104
+ root: { position: "relative", height: "100%", minHeight: 0, display: "flex", flexDirection: "column" },
1105
+ scroller: {
1106
+ flex: 1,
1107
+ overflowY: "auto",
1108
+ padding: tokens9.spacingVerticalM,
1109
+ display: "flex",
1110
+ flexDirection: "column",
1111
+ gap: tokens9.spacingVerticalS
1112
+ },
1113
+ jump: { position: "absolute", bottom: tokens9.spacingVerticalM, alignSelf: "center", zIndex: 1 }
1114
+ });
1115
+ function MessageList(props) {
1116
+ const {
1117
+ messages,
1118
+ participants,
1119
+ busy = false,
1120
+ announcement,
1121
+ groupWindowMs,
1122
+ timeZone,
1123
+ locale,
1124
+ now,
1125
+ emptyState,
1126
+ ...actionHandlers
1127
+ } = props;
1128
+ const styles = useStyles9();
1129
+ const scrollerRef = React10.useRef(null);
1130
+ const { onScroll, scrollToBottom, isPinned } = useStickyScroll(scrollerRef, messages.length);
1131
+ const byId = React10.useMemo(
1132
+ () => new Map((participants ?? []).map((p) => [p.id, p])),
1133
+ [participants]
1134
+ );
1135
+ const runStarts = React10.useMemo(() => {
1136
+ const groups = groupMessages(messages, { windowMs: groupWindowMs, timeZone });
1137
+ return new Set(groups.map((g) => g.messageIds[0]));
1138
+ }, [messages, groupWindowMs, timeZone]);
1139
+ const buckets = React10.useMemo(
1140
+ () => bucketByDay(messages, { timeZone, now }),
1141
+ [messages, timeZone, now]
1142
+ );
1143
+ const dividerBefore = React10.useMemo(() => {
1144
+ const map = /* @__PURE__ */ new Map();
1145
+ for (const bucket of buckets) map.set(bucket.messageIds[0], bucket.label);
1146
+ return map;
1147
+ }, [buckets]);
1148
+ return /* @__PURE__ */ jsxs8("div", { className: styles.root, children: [
1149
+ /* @__PURE__ */ jsx11(LiveAnnouncer, { polite: announcement?.polite, assertive: announcement?.assertive }),
1150
+ /* @__PURE__ */ jsxs8(
1151
+ "div",
1152
+ {
1153
+ ref: scrollerRef,
1154
+ className: styles.scroller,
1155
+ onScroll,
1156
+ role: "log",
1157
+ "aria-label": "Conversation",
1158
+ tabIndex: 0,
1159
+ children: [
1160
+ messages.length === 0 ? emptyState ?? null : messages.map((message) => /* @__PURE__ */ jsxs8(React10.Fragment, { children: [
1161
+ dividerBefore.has(message.id) ? /* @__PURE__ */ jsx11(DayDivider, { label: dividerBefore.get(message.id), locale }) : null,
1162
+ /* @__PURE__ */ jsx11(
1163
+ MessageBubble,
1164
+ {
1165
+ message,
1166
+ author: byId.get(message.authorId),
1167
+ isRunStart: runStarts.has(message.id),
1168
+ locale,
1169
+ ...actionHandlers
1170
+ }
1171
+ )
1172
+ ] }, message.id)),
1173
+ busy ? /* @__PURE__ */ jsx11(TypingIndicator, {}) : null
1174
+ ]
1175
+ }
1176
+ ),
1177
+ !isPinned && messages.length > 0 ? /* @__PURE__ */ jsx11(
1178
+ Button3,
1179
+ {
1180
+ className: styles.jump,
1181
+ size: "small",
1182
+ appearance: "primary",
1183
+ icon: /* @__PURE__ */ jsx11(ArrowDownRegular, {}),
1184
+ onClick: scrollToBottom,
1185
+ children: "Jump to latest"
1186
+ }
1187
+ ) : null
1188
+ ] });
1189
+ }
1190
+
1191
+ // src/conversation/Composer.tsx
1192
+ import * as React11 from "react";
1193
+ import { Button as Button4, Textarea, makeStyles as makeStyles11, tokens as tokens10, Tooltip as Tooltip2 } from "@fluentui/react-components";
1194
+ import { SendRegular, StopRegular } from "@fluentui/react-icons";
1195
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1196
+ var useStyles10 = makeStyles11({
1197
+ root: {
1198
+ display: "flex",
1199
+ gap: tokens10.spacingHorizontalS,
1200
+ alignItems: "flex-end",
1201
+ padding: tokens10.spacingVerticalS,
1202
+ borderTop: `1px solid ${tokens10.colorNeutralStroke2}`
1203
+ },
1204
+ input: { flex: 1 },
1205
+ hint: { fontSize: tokens10.fontSizeBase100, color: tokens10.colorNeutralForeground3 }
1206
+ });
1207
+ function Composer(props) {
1208
+ const {
1209
+ value,
1210
+ onChange,
1211
+ onSend,
1212
+ onStop,
1213
+ busy = false,
1214
+ disabled = false,
1215
+ placeholder = "Type a message",
1216
+ maxLength,
1217
+ sendOn = "enter",
1218
+ toolbar
1219
+ } = props;
1220
+ const styles = useStyles10();
1221
+ const hintId = React11.useId();
1222
+ const submit = () => {
1223
+ const text = value.trim();
1224
+ if (!text || busy || disabled) return;
1225
+ onSend(text);
1226
+ };
1227
+ const onKeyDown = (event) => {
1228
+ if (event.key !== "Enter") return;
1229
+ const wants = sendOn === "enter" ? !event.shiftKey : event.metaKey || event.ctrlKey;
1230
+ if (!wants) return;
1231
+ event.preventDefault();
1232
+ submit();
1233
+ };
1234
+ const hint = sendOn === "enter" ? "Press Enter to send, Shift plus Enter for a new line." : "Press Control or Command plus Enter to send.";
1235
+ return /* @__PURE__ */ jsxs9("div", { className: styles.root, children: [
1236
+ /* @__PURE__ */ jsxs9("div", { className: styles.input, children: [
1237
+ /* @__PURE__ */ jsx12(
1238
+ Textarea,
1239
+ {
1240
+ value,
1241
+ onChange: (_, data) => onChange(data.value),
1242
+ onKeyDown,
1243
+ placeholder,
1244
+ disabled,
1245
+ maxLength,
1246
+ resize: "vertical",
1247
+ "aria-label": placeholder,
1248
+ "aria-describedby": hintId,
1249
+ textarea: { "aria-multiline": true }
1250
+ }
1251
+ ),
1252
+ /* @__PURE__ */ jsx12("span", { id: hintId, className: styles.hint, children: hint })
1253
+ ] }),
1254
+ toolbar,
1255
+ busy && onStop ? /* @__PURE__ */ jsx12(Tooltip2, { content: "Stop generating", relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx12(Button4, { appearance: "secondary", icon: /* @__PURE__ */ jsx12(StopRegular, {}), onClick: onStop }) }) : /* @__PURE__ */ jsx12(Tooltip2, { content: "Send", relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx12(
1256
+ Button4,
1257
+ {
1258
+ appearance: "primary",
1259
+ icon: /* @__PURE__ */ jsx12(SendRegular, {}),
1260
+ onClick: submit,
1261
+ disabled: !value.trim() || busy || disabled
1262
+ }
1263
+ ) })
1264
+ ] });
1265
+ }
1266
+
1267
+ // src/conversation/SuggestedPrompts.tsx
1268
+ import { Button as Button5, makeStyles as makeStyles12, tokens as tokens11 } from "@fluentui/react-components";
1269
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1270
+ var useStyles11 = makeStyles12({
1271
+ root: { display: "flex", flexWrap: "wrap", gap: tokens11.spacingHorizontalXS, padding: tokens11.spacingVerticalXS },
1272
+ heading: { width: "100%", fontSize: tokens11.fontSizeBase200, color: tokens11.colorNeutralForeground3 }
1273
+ });
1274
+ function SuggestedPrompts({
1275
+ prompts,
1276
+ onPick,
1277
+ heading = "Suggested follow-ups"
1278
+ }) {
1279
+ const styles = useStyles11();
1280
+ if (prompts.length === 0) return null;
1281
+ return /* @__PURE__ */ jsxs10("div", { className: styles.root, role: "group", "aria-label": heading, children: [
1282
+ /* @__PURE__ */ jsx13("span", { className: styles.heading, children: heading }),
1283
+ prompts.map((prompt) => /* @__PURE__ */ jsx13(Button5, { size: "small", appearance: "outline", onClick: () => onPick(prompt), children: prompt }, prompt))
1284
+ ] });
1285
+ }
1286
+
1287
+ // src/conversation/EmptyConversation.tsx
1288
+ import { makeStyles as makeStyles13, tokens as tokens12, Text as Text2 } from "@fluentui/react-components";
1289
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1290
+ var useStyles12 = makeStyles13({
1291
+ root: {
1292
+ display: "flex",
1293
+ flexDirection: "column",
1294
+ alignItems: "center",
1295
+ justifyContent: "center",
1296
+ gap: tokens12.spacingVerticalS,
1297
+ height: "100%",
1298
+ padding: tokens12.spacingVerticalXXL,
1299
+ textAlign: "center",
1300
+ color: tokens12.colorNeutralForeground3
1301
+ }
1302
+ });
1303
+ function EmptyConversation({
1304
+ title = "Ask the agent",
1305
+ description = "It answers from the records you have open.",
1306
+ prompts = [],
1307
+ onPick
1308
+ }) {
1309
+ const styles = useStyles12();
1310
+ return /* @__PURE__ */ jsxs11("div", { className: styles.root, children: [
1311
+ /* @__PURE__ */ jsx14(Text2, { as: "h2", size: 500, weight: "semibold", children: title }),
1312
+ /* @__PURE__ */ jsx14(Text2, { children: description }),
1313
+ onPick && prompts.length > 0 ? /* @__PURE__ */ jsx14(SuggestedPrompts, { prompts, onPick, heading: "Try one of these" }) : null
1314
+ ] });
1315
+ }
1316
+
1317
+ // src/conversation/SystemNotice.tsx
1318
+ import {
1319
+ MessageBar,
1320
+ MessageBarBody,
1321
+ MessageBarTitle,
1322
+ MessageBarActions,
1323
+ Button as Button6
1324
+ } from "@fluentui/react-components";
1325
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1326
+ var INTENT = { success: "success", warning: "warning", error: "error", info: "info" };
1327
+ function SystemNotice({ failure, guarded, onRetry }) {
1328
+ if (!failure && !guarded) return null;
1329
+ if (!failure && guarded) {
1330
+ return /* @__PURE__ */ jsx15(MessageBar, { intent: "warning", children: /* @__PURE__ */ jsxs12(MessageBarBody, { children: [
1331
+ /* @__PURE__ */ jsx15(MessageBarTitle, { children: "Response withheld" }),
1332
+ "The provider substituted safe text for the generated answer."
1333
+ ] }) });
1334
+ }
1335
+ if (!failure) return null;
1336
+ const title = {
1337
+ blocked: "Response withheld",
1338
+ refused: "The agent declined",
1339
+ error: "Something went wrong",
1340
+ notProvisioned: "Not configured",
1341
+ cancelled: "Stopped"
1342
+ };
1343
+ return /* @__PURE__ */ jsxs12(MessageBar, { intent: INTENT[runStatusSeverity(failure.status)], children: [
1344
+ /* @__PURE__ */ jsxs12(MessageBarBody, { children: [
1345
+ /* @__PURE__ */ jsx15(MessageBarTitle, { children: title[failure.status] ?? "Notice" }),
1346
+ failure.message
1347
+ ] }),
1348
+ failure.recoverable && onRetry ? /* @__PURE__ */ jsx15(MessageBarActions, { children: /* @__PURE__ */ jsx15(Button6, { size: "small", onClick: onRetry, children: "Try again" }) }) : null
1349
+ ] });
1350
+ }
1351
+
1352
+ // src/ops/AgentStatusBadge.tsx
1353
+ import { Badge, Tooltip as Tooltip3 } from "@fluentui/react-components";
1354
+ import {
1355
+ CheckmarkCircleRegular,
1356
+ ShieldErrorRegular,
1357
+ DismissCircleRegular,
1358
+ ErrorCircleRegular,
1359
+ PlugDisconnectedRegular,
1360
+ StopRegular as StopRegular2
1361
+ } from "@fluentui/react-icons";
1362
+ import { jsx as jsx16 } from "react/jsx-runtime";
1363
+ var PRESENTATION = {
1364
+ succeeded: { label: "Succeeded", color: "success", icon: /* @__PURE__ */ jsx16(CheckmarkCircleRegular, {}) },
1365
+ blocked: { label: "Blocked by content filter", color: "warning", icon: /* @__PURE__ */ jsx16(ShieldErrorRegular, {}) },
1366
+ refused: { label: "Declined", color: "warning", icon: /* @__PURE__ */ jsx16(DismissCircleRegular, {}) },
1367
+ error: { label: "Error", color: "danger", icon: /* @__PURE__ */ jsx16(ErrorCircleRegular, {}) },
1368
+ notProvisioned: { label: "Not configured", color: "informative", icon: /* @__PURE__ */ jsx16(PlugDisconnectedRegular, {}) },
1369
+ cancelled: { label: "Stopped", color: "subtle", icon: /* @__PURE__ */ jsx16(StopRegular2, {}) }
1370
+ };
1371
+ function runStatusLabel(status) {
1372
+ return PRESENTATION[status].label;
1373
+ }
1374
+ function AgentStatusBadge({ status, reason, size = "medium" }) {
1375
+ const { label, color, icon } = PRESENTATION[status];
1376
+ const badge = /* @__PURE__ */ jsx16(Badge, { appearance: "tint", color, icon, size, children: label });
1377
+ return reason ? /* @__PURE__ */ jsx16(Tooltip3, { content: reason, relationship: "description", withArrow: true, children: badge }) : badge;
1378
+ }
1379
+
1380
+ // src/ops/AvailabilityBanner.tsx
1381
+ import { MessageBar as MessageBar2, MessageBarBody as MessageBarBody2, MessageBarTitle as MessageBarTitle2, MessageBarActions as MessageBarActions2, Button as Button7 } from "@fluentui/react-components";
1382
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1383
+ function AvailabilityBanner({
1384
+ availability,
1385
+ onRetry,
1386
+ actions
1387
+ }) {
1388
+ if (!availability || availability.available) return null;
1389
+ const notProvisioned = availability.status === "notProvisioned";
1390
+ return /* @__PURE__ */ jsxs13(MessageBar2, { intent: notProvisioned ? "info" : "warning", children: [
1391
+ /* @__PURE__ */ jsxs13(MessageBarBody2, { children: [
1392
+ /* @__PURE__ */ jsx17(MessageBarTitle2, { children: notProvisioned ? "No agent configured" : "The agent is unavailable" }),
1393
+ availability.reason ?? "No reason was reported."
1394
+ ] }),
1395
+ onRetry || actions ? /* @__PURE__ */ jsxs13(MessageBarActions2, { children: [
1396
+ actions,
1397
+ onRetry ? /* @__PURE__ */ jsx17(Button7, { size: "small", onClick: onRetry, children: "Check again" }) : null
1398
+ ] }) : null
1399
+ ] });
1400
+ }
1401
+
1402
+ // src/ops/UsageMeter.tsx
1403
+ import { makeStyles as makeStyles14, tokens as tokens13 } from "@fluentui/react-components";
1404
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1405
+ var useStyles13 = makeStyles14({
1406
+ root: {
1407
+ display: "flex",
1408
+ flexWrap: "wrap",
1409
+ gap: tokens13.spacingHorizontalM,
1410
+ fontSize: tokens13.fontSizeBase100,
1411
+ color: tokens13.colorNeutralForeground3
1412
+ },
1413
+ item: { display: "flex", gap: tokens13.spacingHorizontalXXS },
1414
+ value: { fontFamily: tokens13.fontFamilyMonospace },
1415
+ unknown: { fontStyle: "italic" }
1416
+ });
1417
+ function UsageMeter({
1418
+ usage,
1419
+ show = ["tokens", "cost", "duration"]
1420
+ }) {
1421
+ const styles = useStyles13();
1422
+ if (!usage) return null;
1423
+ const items = [];
1424
+ if (show.includes("tokens")) {
1425
+ const known = usage.inputTokens !== void 0 || usage.outputTokens !== void 0;
1426
+ items.push({
1427
+ key: "tokens",
1428
+ label: "Tokens",
1429
+ value: known ? `${usage.inputTokens ?? 0} in / ${usage.outputTokens ?? 0} out${usage.cachedTokens ? ` / ${usage.cachedTokens} cached` : ""}` : "not reported",
1430
+ known
1431
+ });
1432
+ }
1433
+ if (show.includes("cost")) {
1434
+ const known = usage.costUsd !== void 0;
1435
+ items.push({
1436
+ key: "cost",
1437
+ label: "Cost",
1438
+ value: known ? `$${usage.costUsd.toFixed(6)}` : "not reported",
1439
+ known
1440
+ });
1441
+ }
1442
+ if (show.includes("duration")) {
1443
+ const known = usage.durationMs !== void 0;
1444
+ items.push({
1445
+ key: "duration",
1446
+ label: "Duration",
1447
+ value: known ? `${usage.durationMs} ms` : "not reported",
1448
+ known
1449
+ });
1450
+ }
1451
+ if (show.includes("model") && usage.model) {
1452
+ items.push({ key: "model", label: "Model", value: usage.model, known: true });
1453
+ }
1454
+ return /* @__PURE__ */ jsx18("div", { className: styles.root, children: items.map((item) => /* @__PURE__ */ jsxs14("span", { className: styles.item, children: [
1455
+ item.label,
1456
+ ":",
1457
+ " ",
1458
+ /* @__PURE__ */ jsx18("span", { className: item.known ? styles.value : styles.unknown, children: item.value })
1459
+ ] }, item.key)) });
1460
+ }
1461
+
1462
+ // src/ops/ThrottleNotice.tsx
1463
+ import * as React12 from "react";
1464
+ import { MessageBar as MessageBar3, MessageBarBody as MessageBarBody3, MessageBarTitle as MessageBarTitle3, MessageBarActions as MessageBarActions3, Button as Button8 } from "@fluentui/react-components";
1465
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1466
+ function ThrottleNotice({ failure, onRetry, now = Date.now }) {
1467
+ const retryAfterMs = failure?.retryAfterMs;
1468
+ const eligible = Boolean(failure?.recoverable && retryAfterMs && retryAfterMs > 0);
1469
+ const [remaining, setRemaining] = React12.useState(retryAfterMs ?? 0);
1470
+ React12.useEffect(() => {
1471
+ if (!eligible) return;
1472
+ const deadline = now() + (retryAfterMs ?? 0);
1473
+ setRemaining(retryAfterMs ?? 0);
1474
+ const id = setInterval(() => {
1475
+ const left = deadline - now();
1476
+ setRemaining(left > 0 ? left : 0);
1477
+ if (left <= 0) clearInterval(id);
1478
+ }, 250);
1479
+ return () => clearInterval(id);
1480
+ }, [eligible, retryAfterMs, now]);
1481
+ if (!eligible || !failure) return null;
1482
+ const seconds = Math.ceil(remaining / 1e3);
1483
+ const ready = remaining <= 0;
1484
+ return /* @__PURE__ */ jsxs15(MessageBar3, { intent: "warning", children: [
1485
+ /* @__PURE__ */ jsxs15(MessageBarBody3, { children: [
1486
+ /* @__PURE__ */ jsx19(MessageBarTitle3, { children: "Rate limited" }),
1487
+ ready ? "You can try again now." : `${failure.message} Try again in ${seconds}s.`
1488
+ ] }),
1489
+ onRetry ? /* @__PURE__ */ jsx19(MessageBarActions3, { children: /* @__PURE__ */ jsx19(Button8, { size: "small", onClick: onRetry, disabled: !ready, children: "Try again" }) }) : null
1490
+ ] });
1491
+ }
1492
+
1493
+ // src/ops/ModelBadge.tsx
1494
+ import { Badge as Badge2 } from "@fluentui/react-components";
1495
+ import { jsx as jsx20 } from "react/jsx-runtime";
1496
+ function ModelBadge({ provider, model, version }) {
1497
+ const parts = [provider, model, version ? `v${version}` : void 0].filter(Boolean);
1498
+ if (parts.length === 0) return null;
1499
+ return /* @__PURE__ */ jsx20(Badge2, { appearance: "outline", color: "informative", children: parts.join(" \xB7 ") });
1500
+ }
1501
+
1502
+ // src/ops/AgentPicker.tsx
1503
+ import { Dropdown, Option, Field } from "@fluentui/react-components";
1504
+ import { jsx as jsx21 } from "react/jsx-runtime";
1505
+ function AgentPicker({
1506
+ agents,
1507
+ selectedId,
1508
+ onSelect,
1509
+ label = "Agent",
1510
+ disabled
1511
+ }) {
1512
+ const selected = agents.find((a) => a.id === selectedId);
1513
+ return /* @__PURE__ */ jsx21(Field, { label, children: /* @__PURE__ */ jsx21(
1514
+ Dropdown,
1515
+ {
1516
+ disabled,
1517
+ value: selected ? selected.name : "",
1518
+ selectedOptions: selectedId ? [selectedId] : [],
1519
+ onOptionSelect: (_, data) => {
1520
+ if (data.optionValue) onSelect(data.optionValue);
1521
+ },
1522
+ placeholder: "Select an agent",
1523
+ children: agents.map((agent) => /* @__PURE__ */ jsx21(Option, { value: agent.id, text: agent.name, children: agent.model ? `${agent.name} \u2014 ${agent.model}` : agent.name }, agent.id))
1524
+ }
1525
+ ) });
1526
+ }
1527
+
1528
+ // src/ops/RunTimeline.tsx
1529
+ import * as React13 from "react";
1530
+ import { makeStyles as makeStyles15, tokens as tokens14, Text as Text3 } from "@fluentui/react-components";
1531
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
1532
+ var useStyles14 = makeStyles15({
1533
+ list: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: tokens14.spacingVerticalXS },
1534
+ step: { display: "flex", gap: tokens14.spacingHorizontalS, alignItems: "baseline" },
1535
+ index: { fontFamily: tokens14.fontFamilyMonospace, fontSize: tokens14.fontSizeBase100, color: tokens14.colorNeutralForeground3, minWidth: "18px" },
1536
+ name: { fontFamily: tokens14.fontFamilyMonospace, fontSize: tokens14.fontSizeBase200 },
1537
+ status: { fontSize: tokens14.fontSizeBase100, color: tokens14.colorNeutralForeground3 },
1538
+ section: { marginTop: tokens14.spacingVerticalS }
1539
+ });
1540
+ var TOOL_STATUS = {
1541
+ proposed: "proposed",
1542
+ running: "running",
1543
+ succeeded: "succeeded",
1544
+ failed: "failed",
1545
+ denied: "denied"
1546
+ };
1547
+ function RunTimeline({ message, defaultOpen = false }) {
1548
+ const styles = useStyles14();
1549
+ const id = React13.useId();
1550
+ const calls = message.toolCalls ?? [];
1551
+ const sources = message.sources ?? [];
1552
+ const hasAnything = calls.length > 0 || sources.length > 0 || message.usage || message.failure;
1553
+ if (!hasAnything) return null;
1554
+ const status = message.failure?.status ?? (message.guarded ? "blocked" : "succeeded");
1555
+ return /* @__PURE__ */ jsxs16(
1556
+ Disclosure,
1557
+ {
1558
+ id,
1559
+ defaultOpen,
1560
+ summary: `Trace \u2014 ${calls.length} tool call${calls.length === 1 ? "" : "s"}`,
1561
+ children: [
1562
+ /* @__PURE__ */ jsx22("div", { className: styles.step, children: /* @__PURE__ */ jsx22(AgentStatusBadge, { status, reason: message.failure?.message, size: "small" }) }),
1563
+ calls.length > 0 ? /* @__PURE__ */ jsx22("ol", { className: styles.list, "aria-label": "Tool calls", children: calls.map((call, index) => /* @__PURE__ */ jsxs16("li", { className: styles.step, children: [
1564
+ /* @__PURE__ */ jsxs16("span", { className: styles.index, children: [
1565
+ index + 1,
1566
+ "."
1567
+ ] }),
1568
+ /* @__PURE__ */ jsx22("span", { className: styles.name, children: call.name }),
1569
+ /* @__PURE__ */ jsx22("span", { className: styles.status, children: TOOL_STATUS[call.status] })
1570
+ ] }, call.id)) }) : null,
1571
+ sources.length > 0 ? /* @__PURE__ */ jsxs16(Text3, { as: "p", size: 200, className: styles.section, children: [
1572
+ sources.length,
1573
+ " source",
1574
+ sources.length === 1 ? "" : "s",
1575
+ " retrieved"
1576
+ ] }) : null,
1577
+ /* @__PURE__ */ jsx22("div", { className: styles.section, children: /* @__PURE__ */ jsx22(UsageMeter, { usage: message.usage, show: ["tokens", "cost", "duration", "model"] }) })
1578
+ ]
1579
+ }
1580
+ );
1581
+ }
1582
+
1583
+ // src/ops/RunHistoryPanel.tsx
1584
+ import { makeStyles as makeStyles16, tokens as tokens15, Text as Text4 } from "@fluentui/react-components";
1585
+ import { Fragment as Fragment3, jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
1586
+ var useStyles15 = makeStyles16({
1587
+ list: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: tokens15.spacingVerticalXS },
1588
+ row: {
1589
+ display: "flex",
1590
+ flexDirection: "column",
1591
+ gap: "2px",
1592
+ padding: tokens15.spacingVerticalXS,
1593
+ borderBottom: `1px solid ${tokens15.colorNeutralStroke2}`
1594
+ },
1595
+ head: { display: "flex", alignItems: "center", gap: tokens15.spacingHorizontalS, flexWrap: "wrap" },
1596
+ time: { fontSize: tokens15.fontSizeBase100, color: tokens15.colorNeutralForeground3 },
1597
+ prompt: { fontSize: tokens15.fontSizeBase200, color: tokens15.colorNeutralForeground2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
1598
+ id: { fontFamily: tokens15.fontFamilyMonospace, fontSize: tokens15.fontSizeBase100, color: tokens15.colorNeutralForeground3 },
1599
+ empty: { padding: tokens15.spacingVerticalM, color: tokens15.colorNeutralForeground3 }
1600
+ });
1601
+ function RunHistoryPanel({
1602
+ runs,
1603
+ grid,
1604
+ locale,
1605
+ emptyLabel = "No runs yet."
1606
+ }) {
1607
+ const styles = useStyles15();
1608
+ if (grid) return /* @__PURE__ */ jsx23(Fragment3, { children: grid });
1609
+ if (runs.length === 0) {
1610
+ return /* @__PURE__ */ jsx23(Text4, { className: styles.empty, size: 200, children: emptyLabel });
1611
+ }
1612
+ return /* @__PURE__ */ jsx23("ul", { className: styles.list, "aria-label": "Run history", children: runs.map((run) => /* @__PURE__ */ jsxs17("li", { className: styles.row, children: [
1613
+ /* @__PURE__ */ jsxs17("div", { className: styles.head, children: [
1614
+ /* @__PURE__ */ jsx23(AgentStatusBadge, { status: run.status, size: "small" }),
1615
+ /* @__PURE__ */ jsx23("span", { className: styles.time, children: new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "short" }).format(
1616
+ new Date(run.startedAt)
1617
+ ) }),
1618
+ run.correlationId ? /* @__PURE__ */ jsx23("span", { className: styles.id, children: run.correlationId }) : null
1619
+ ] }),
1620
+ run.prompt ? /* @__PURE__ */ jsx23("span", { className: styles.prompt, children: run.prompt }) : null,
1621
+ /* @__PURE__ */ jsx23(UsageMeter, { usage: run.usage })
1622
+ ] }, run.id)) });
1623
+ }
1624
+
1625
+ // src/ops/AgentOpsPanel.tsx
1626
+ import { makeStyles as makeStyles17, tokens as tokens16, Text as Text5 } from "@fluentui/react-components";
1627
+ import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
1628
+ var useStyles16 = makeStyles17({
1629
+ root: { display: "flex", flexDirection: "column", gap: tokens16.spacingVerticalM, minWidth: 0 },
1630
+ head: { display: "flex", alignItems: "center", gap: tokens16.spacingHorizontalS, flexWrap: "wrap" },
1631
+ section: { display: "flex", flexDirection: "column", gap: tokens16.spacingVerticalXS },
1632
+ caps: { fontSize: tokens16.fontSizeBase100, color: tokens16.colorNeutralForeground3 }
1633
+ });
1634
+ function AgentOpsPanel(props) {
1635
+ const { availability, agents, selectedAgentId, onSelectAgent, runs = [], runsGrid, onRefresh, locale } = props;
1636
+ const styles = useStyles16();
1637
+ const selected = agents?.find((a) => a.id === selectedAgentId);
1638
+ const caps = availability?.capabilities;
1639
+ return /* @__PURE__ */ jsxs18("div", { className: styles.root, children: [
1640
+ /* @__PURE__ */ jsx24(AvailabilityBanner, { availability, onRetry: onRefresh }),
1641
+ agents && agents.length > 0 && onSelectAgent ? /* @__PURE__ */ jsxs18("div", { className: styles.section, children: [
1642
+ /* @__PURE__ */ jsx24(AgentPicker, { agents, selectedId: selectedAgentId, onSelect: onSelectAgent }),
1643
+ /* @__PURE__ */ jsx24("div", { className: styles.head, children: /* @__PURE__ */ jsx24(ModelBadge, { provider: selected?.name, model: selected?.model, version: selected?.version }) })
1644
+ ] }) : null,
1645
+ caps ? /* @__PURE__ */ jsxs18("span", { className: styles.caps, children: [
1646
+ "Supports:",
1647
+ " ",
1648
+ Object.entries(caps).filter(([, on]) => on).map(([name]) => name).join(", ") || "nothing reported"
1649
+ ] }) : null,
1650
+ /* @__PURE__ */ jsxs18("div", { className: styles.section, children: [
1651
+ /* @__PURE__ */ jsx24(Text5, { weight: "semibold", size: 300, children: "Recent runs" }),
1652
+ /* @__PURE__ */ jsx24(RunHistoryPanel, { runs, grid: runsGrid, locale })
1653
+ ] })
1654
+ ] });
1655
+ }
1656
+
1657
+ // src/shell/AgentAppShell.tsx
1658
+ import * as React14 from "react";
1659
+ import { makeStyles as makeStyles18, tokens as tokens17, Button as Button9 } from "@fluentui/react-components";
1660
+ import { NavigationRegular } from "@fluentui/react-icons";
1661
+ import { BREAKPOINTS, resolveBreakpoint, useContainerWidth } from "@dataverse-kit/surface-kit/v9";
1662
+ import { Fragment as Fragment4, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
1663
+ var useStyles17 = makeStyles18({
1664
+ root: {
1665
+ display: "flex",
1666
+ height: "100%",
1667
+ minHeight: 0,
1668
+ backgroundColor: tokens17.colorNeutralBackground2,
1669
+ position: "relative",
1670
+ overflow: "hidden"
1671
+ },
1672
+ sidebar: {
1673
+ display: "flex",
1674
+ flexDirection: "column",
1675
+ minHeight: 0,
1676
+ backgroundColor: tokens17.colorNeutralBackground1,
1677
+ borderRight: `1px solid ${tokens17.colorNeutralStroke2}`
1678
+ },
1679
+ // Below `md` the list overlays the conversation instead of shrinking it to nothing.
1680
+ sidebarOverlay: {
1681
+ position: "absolute",
1682
+ top: 0,
1683
+ bottom: 0,
1684
+ left: 0,
1685
+ zIndex: 2,
1686
+ boxShadow: tokens17.shadow16
1687
+ },
1688
+ scrim: { position: "absolute", inset: "0", zIndex: 1, backgroundColor: tokens17.colorBackgroundOverlay },
1689
+ main: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0, minHeight: 0 },
1690
+ body: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column" },
1691
+ toggle: { position: "absolute", top: tokens17.spacingVerticalS, left: tokens17.spacingHorizontalS, zIndex: 3 }
1692
+ });
1693
+ function AgentAppShell(props) {
1694
+ const {
1695
+ rail,
1696
+ sidebar,
1697
+ header,
1698
+ children,
1699
+ rightPane,
1700
+ sidebarWidth = 280,
1701
+ sidebarOpen,
1702
+ onSidebarOpenChange,
1703
+ label = "Agent workspace"
1704
+ } = props;
1705
+ const styles = useStyles17();
1706
+ const [containerRef, measuredWidth] = useContainerWidth();
1707
+ const width = props.containerWidth ?? (measuredWidth > 0 ? measuredWidth : void 0);
1708
+ const effective = width ?? BREAKPOINTS.xl;
1709
+ const breakpoint = resolveBreakpoint(effective);
1710
+ const railVisible = effective >= BREAKPOINTS.sm;
1711
+ const sidebarInline = effective >= BREAKPOINTS.md;
1712
+ const rightPaneVisible = effective >= BREAKPOINTS.lg;
1713
+ const [uncontrolledOpen, setUncontrolledOpen] = React14.useState(false);
1714
+ const overlayOpen = sidebarOpen ?? uncontrolledOpen;
1715
+ const setOverlayOpen = (open) => {
1716
+ onSidebarOpenChange?.(open);
1717
+ if (sidebarOpen === void 0) setUncontrolledOpen(open);
1718
+ };
1719
+ return /* @__PURE__ */ jsxs19("div", { ref: containerRef, className: styles.root, "data-breakpoint": breakpoint, "aria-label": label, children: [
1720
+ rail && railVisible ? rail : null,
1721
+ sidebar && sidebarInline ? /* @__PURE__ */ jsx25("div", { className: styles.sidebar, style: { width: sidebarWidth }, children: sidebar }) : null,
1722
+ sidebar && !sidebarInline && overlayOpen ? /* @__PURE__ */ jsxs19(Fragment4, { children: [
1723
+ /* @__PURE__ */ jsx25(
1724
+ "div",
1725
+ {
1726
+ className: styles.scrim,
1727
+ onClick: () => setOverlayOpen(false),
1728
+ "aria-hidden": "true"
1729
+ }
1730
+ ),
1731
+ /* @__PURE__ */ jsx25("div", { className: `${styles.sidebar} ${styles.sidebarOverlay}`, style: { width: sidebarWidth }, children: sidebar })
1732
+ ] }) : null,
1733
+ /* @__PURE__ */ jsxs19("div", { className: styles.main, children: [
1734
+ sidebar && !sidebarInline ? /* @__PURE__ */ jsx25(
1735
+ Button9,
1736
+ {
1737
+ className: styles.toggle,
1738
+ size: "small",
1739
+ appearance: "subtle",
1740
+ icon: /* @__PURE__ */ jsx25(NavigationRegular, {}),
1741
+ "aria-label": overlayOpen ? "Hide conversations" : "Show conversations",
1742
+ "aria-expanded": overlayOpen,
1743
+ onClick: () => setOverlayOpen(!overlayOpen)
1744
+ }
1745
+ ) : null,
1746
+ header,
1747
+ /* @__PURE__ */ jsx25("div", { className: styles.body, children })
1748
+ ] }),
1749
+ rightPaneVisible ? rightPane : null
1750
+ ] });
1751
+ }
1752
+
1753
+ // src/shell/AppRail.tsx
1754
+ import {
1755
+ Button as Button10,
1756
+ Menu,
1757
+ MenuTrigger,
1758
+ MenuPopover,
1759
+ MenuList,
1760
+ MenuItem,
1761
+ Tooltip as Tooltip4,
1762
+ makeStyles as makeStyles19,
1763
+ mergeClasses as mergeClasses5,
1764
+ tokens as tokens18
1765
+ } from "@fluentui/react-components";
1766
+ import { MoreHorizontalRegular } from "@fluentui/react-icons";
1767
+ import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
1768
+ var useStyles18 = makeStyles19({
1769
+ root: {
1770
+ display: "flex",
1771
+ flexDirection: "column",
1772
+ alignItems: "center",
1773
+ gap: tokens18.spacingVerticalXS,
1774
+ padding: tokens18.spacingVerticalS,
1775
+ backgroundColor: tokens18.colorNeutralBackground3,
1776
+ borderRight: `1px solid ${tokens18.colorNeutralStroke2}`,
1777
+ minHeight: 0
1778
+ },
1779
+ spacer: { flex: 1 },
1780
+ button: {
1781
+ position: "relative",
1782
+ ":focus-visible": { outline: `2px solid ${tokens18.colorStrokeFocus2}`, outlineOffset: "1px" }
1783
+ },
1784
+ selected: { backgroundColor: tokens18.colorNeutralBackground1Selected },
1785
+ badge: {
1786
+ position: "absolute",
1787
+ top: "2px",
1788
+ right: "2px",
1789
+ minWidth: "16px",
1790
+ height: "16px",
1791
+ padding: "0 3px",
1792
+ borderRadius: tokens18.borderRadiusCircular,
1793
+ backgroundColor: tokens18.colorPaletteRedBackground3,
1794
+ color: tokens18.colorNeutralForegroundOnBrand,
1795
+ fontSize: "10px",
1796
+ lineHeight: "16px"
1797
+ }
1798
+ });
1799
+ function AppRail(props) {
1800
+ const { items, selectedId, onSelect, maxVisible = 6, label = "Apps", footer } = props;
1801
+ const styles = useStyles18();
1802
+ const visible = items.slice(0, maxVisible);
1803
+ const overflow = items.slice(maxVisible);
1804
+ return /* @__PURE__ */ jsxs20("nav", { className: styles.root, "aria-label": label, children: [
1805
+ visible.map((item) => /* @__PURE__ */ jsx26(Tooltip4, { content: item.label, relationship: "label", positioning: "after", withArrow: true, children: /* @__PURE__ */ jsx26(
1806
+ Button10,
1807
+ {
1808
+ appearance: "subtle",
1809
+ icon: item.icon,
1810
+ className: mergeClasses5(styles.button, item.id === selectedId && styles.selected),
1811
+ "aria-pressed": item.id === selectedId,
1812
+ "aria-label": item.badgeCount ? `${item.label}, ${item.badgeCount} unread` : item.label,
1813
+ onClick: () => onSelect(item.id),
1814
+ children: item.badgeCount ? /* @__PURE__ */ jsx26("span", { className: styles.badge, "aria-hidden": "true", children: item.badgeCount }) : null
1815
+ }
1816
+ ) }, item.id)),
1817
+ overflow.length > 0 ? /* @__PURE__ */ jsxs20(Menu, { children: [
1818
+ /* @__PURE__ */ jsx26(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx26(
1819
+ Button10,
1820
+ {
1821
+ appearance: "subtle",
1822
+ icon: /* @__PURE__ */ jsx26(MoreHorizontalRegular, {}),
1823
+ className: styles.button,
1824
+ "aria-label": `More apps, ${overflow.length} hidden`
1825
+ }
1826
+ ) }),
1827
+ /* @__PURE__ */ jsx26(MenuPopover, { children: /* @__PURE__ */ jsx26(MenuList, { children: overflow.map((item) => /* @__PURE__ */ jsx26(MenuItem, { icon: item.icon, onClick: () => onSelect(item.id), children: item.label }, item.id)) }) })
1828
+ ] }) : null,
1829
+ /* @__PURE__ */ jsx26("div", { className: styles.spacer }),
1830
+ footer
1831
+ ] });
1832
+ }
1833
+
1834
+ // src/shell/ConversationList.tsx
1835
+ import * as React15 from "react";
1836
+ import { makeStyles as makeStyles21, tokens as tokens20, Text as Text6 } from "@fluentui/react-components";
1837
+
1838
+ // src/shell/ConversationListItem.tsx
1839
+ import { makeStyles as makeStyles20, mergeClasses as mergeClasses6, tokens as tokens19 } from "@fluentui/react-components";
1840
+
1841
+ // src/shell/AgentAvatar.tsx
1842
+ import { Avatar as Avatar2 } from "@fluentui/react-components";
1843
+ import { jsx as jsx27 } from "react/jsx-runtime";
1844
+ var STATUS = {
1845
+ available: "available",
1846
+ busy: "busy",
1847
+ away: "away",
1848
+ offline: "offline",
1849
+ dnd: "do-not-disturb",
1850
+ unknown: "unknown"
1851
+ };
1852
+ function AgentAvatar({ participant, presence, size = 32, name }) {
1853
+ const displayName = name ?? participant?.displayName ?? "Unknown";
1854
+ return /* @__PURE__ */ jsx27(
1855
+ Avatar2,
1856
+ {
1857
+ name: displayName,
1858
+ initials: participant?.initials,
1859
+ image: participant?.imageUrl ? { src: participant.imageUrl } : void 0,
1860
+ size,
1861
+ badge: presence ? { status: STATUS[presence] ?? "unknown" } : void 0,
1862
+ "aria-hidden": void 0
1863
+ }
1864
+ );
1865
+ }
1866
+
1867
+ // src/shell/ConversationListItem.tsx
1868
+ import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
1869
+ var useStyles19 = makeStyles20({
1870
+ row: {
1871
+ display: "flex",
1872
+ gap: tokens19.spacingHorizontalS,
1873
+ alignItems: "center",
1874
+ padding: `${tokens19.spacingVerticalS} ${tokens19.spacingHorizontalS}`,
1875
+ borderRadius: tokens19.borderRadiusMedium,
1876
+ cursor: "pointer",
1877
+ ":hover": { backgroundColor: tokens19.colorNeutralBackground1Hover }
1878
+ },
1879
+ // ★ The ACTIVE row (aria-activedescendant target) gets a visible focus indicator, because
1880
+ // real DOM focus stays on the listbox — without this the keyboard user sees nothing move.
1881
+ active: { outline: `2px solid ${tokens19.colorStrokeFocus2}`, outlineOffset: "-2px" },
1882
+ selected: { backgroundColor: tokens19.colorNeutralBackground1Selected },
1883
+ body: { flex: 1, minWidth: 0 },
1884
+ title: {
1885
+ display: "block",
1886
+ fontSize: tokens19.fontSizeBase300,
1887
+ fontWeight: tokens19.fontWeightSemibold,
1888
+ overflow: "hidden",
1889
+ textOverflow: "ellipsis",
1890
+ whiteSpace: "nowrap"
1891
+ },
1892
+ muted: { fontWeight: tokens19.fontWeightRegular, color: tokens19.colorNeutralForeground3 },
1893
+ preview: {
1894
+ display: "block",
1895
+ fontSize: tokens19.fontSizeBase200,
1896
+ color: tokens19.colorNeutralForeground3,
1897
+ overflow: "hidden",
1898
+ textOverflow: "ellipsis",
1899
+ whiteSpace: "nowrap"
1900
+ },
1901
+ meta: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "2px" },
1902
+ time: { fontSize: tokens19.fontSizeBase100, color: tokens19.colorNeutralForeground3 },
1903
+ unread: {
1904
+ minWidth: "18px",
1905
+ height: "18px",
1906
+ padding: `0 ${tokens19.spacingHorizontalXXS}`,
1907
+ borderRadius: tokens19.borderRadiusCircular,
1908
+ backgroundColor: tokens19.colorBrandBackground,
1909
+ color: tokens19.colorNeutralForegroundOnBrand,
1910
+ fontSize: tokens19.fontSizeBase100,
1911
+ lineHeight: "18px",
1912
+ textAlign: "center"
1913
+ }
1914
+ });
1915
+ function conversationAccessibleName(summary, locale) {
1916
+ const parts = [summary.title];
1917
+ if (summary.presence && summary.presence !== "unknown") parts.push(summary.presence);
1918
+ if (!summary.muted && summary.unreadCount) {
1919
+ parts.push(`${summary.unreadCount} unread`);
1920
+ }
1921
+ if (summary.muted) parts.push("muted");
1922
+ if (summary.timestamp) {
1923
+ parts.push(new Intl.DateTimeFormat(locale, { timeStyle: "short" }).format(new Date(summary.timestamp)));
1924
+ }
1925
+ return parts.join(", ");
1926
+ }
1927
+ function ConversationListItem(props) {
1928
+ const { summary, selected, active, id, locale, onSelect } = props;
1929
+ const styles = useStyles19();
1930
+ const time = summary.timestamp ? new Intl.DateTimeFormat(locale, { timeStyle: "short" }).format(new Date(summary.timestamp)) : void 0;
1931
+ return /* @__PURE__ */ jsxs21(
1932
+ "div",
1933
+ {
1934
+ id,
1935
+ role: "option",
1936
+ "aria-selected": selected,
1937
+ "aria-label": conversationAccessibleName(summary, locale),
1938
+ className: mergeClasses6(styles.row, selected && styles.selected, active && styles.active),
1939
+ onClick: () => onSelect(summary.id),
1940
+ "data-conversation-option": true,
1941
+ children: [
1942
+ /* @__PURE__ */ jsx28(AgentAvatar, { participant: summary.participant, presence: summary.presence, size: 32 }),
1943
+ /* @__PURE__ */ jsxs21("div", { className: styles.body, "aria-hidden": "true", children: [
1944
+ /* @__PURE__ */ jsx28("span", { className: mergeClasses6(styles.title, summary.muted && styles.muted), children: summary.title }),
1945
+ summary.preview ? /* @__PURE__ */ jsx28("span", { className: styles.preview, children: summary.preview }) : null
1946
+ ] }),
1947
+ /* @__PURE__ */ jsxs21("div", { className: styles.meta, "aria-hidden": "true", children: [
1948
+ time ? /* @__PURE__ */ jsx28("span", { className: styles.time, children: time }) : null,
1949
+ !summary.muted && summary.unreadCount ? /* @__PURE__ */ jsx28("span", { className: styles.unread, children: summary.unreadCount }) : null
1950
+ ] })
1951
+ ]
1952
+ }
1953
+ );
1954
+ }
1955
+
1956
+ // src/shell/ConversationList.tsx
1957
+ import { jsx as jsx29 } from "react/jsx-runtime";
1958
+ var useStyles20 = makeStyles21({
1959
+ root: {
1960
+ display: "flex",
1961
+ flexDirection: "column",
1962
+ gap: "2px",
1963
+ padding: tokens20.spacingHorizontalXS,
1964
+ overflowY: "auto",
1965
+ minHeight: 0,
1966
+ ":focus-visible": { outline: `2px solid ${tokens20.colorStrokeFocus2}`, outlineOffset: "-2px" }
1967
+ },
1968
+ empty: { padding: tokens20.spacingVerticalM, color: tokens20.colorNeutralForeground3 }
1969
+ });
1970
+ function ConversationList(props) {
1971
+ const { items, selectedId, onSelect, label = "Conversations", locale, empty } = props;
1972
+ const styles = useStyles20();
1973
+ const listRef = React15.useRef(null);
1974
+ const idBase = React15.useId();
1975
+ const selectedIndex = Math.max(0, items.findIndex((i) => i.id === selectedId));
1976
+ const [active, setActive] = React15.useState(selectedIndex);
1977
+ React15.useEffect(() => {
1978
+ setActive((current) => {
1979
+ if (items.length === 0) return 0;
1980
+ if (selectedIndex >= 0 && items[selectedIndex]) return selectedIndex;
1981
+ return Math.min(current, items.length - 1);
1982
+ });
1983
+ }, [selectedIndex, items]);
1984
+ const optionId = (index) => `${idBase}-opt-${index}`;
1985
+ React15.useEffect(() => {
1986
+ const el = listRef.current?.ownerDocument.getElementById(optionId(active));
1987
+ el?.scrollIntoView?.({ block: "nearest" });
1988
+ }, [active, idBase]);
1989
+ const onKeyDown = (event) => {
1990
+ const last = items.length - 1;
1991
+ if (last < 0) return;
1992
+ let next = null;
1993
+ if (event.key === "ArrowDown") next = Math.min(last, active + 1);
1994
+ else if (event.key === "ArrowUp") next = Math.max(0, active - 1);
1995
+ else if (event.key === "Home") next = 0;
1996
+ else if (event.key === "End") next = last;
1997
+ else if (event.key === "Enter" || event.key === " ") {
1998
+ event.preventDefault();
1999
+ const picked = items[active];
2000
+ if (picked) onSelect(picked.id);
2001
+ return;
2002
+ }
2003
+ if (next === null) return;
2004
+ event.preventDefault();
2005
+ setActive(next);
2006
+ };
2007
+ if (items.length === 0) {
2008
+ return /* @__PURE__ */ jsx29("div", { className: styles.root, children: empty ?? /* @__PURE__ */ jsx29(Text6, { className: styles.empty, size: 200, children: "No conversations yet." }) });
2009
+ }
2010
+ return /* @__PURE__ */ jsx29(
2011
+ "div",
2012
+ {
2013
+ ref: listRef,
2014
+ className: styles.root,
2015
+ role: "listbox",
2016
+ "aria-label": label,
2017
+ tabIndex: 0,
2018
+ "aria-activedescendant": optionId(active),
2019
+ onKeyDown,
2020
+ children: items.map((summary, index) => /* @__PURE__ */ jsx29(
2021
+ ConversationListItem,
2022
+ {
2023
+ id: optionId(index),
2024
+ summary,
2025
+ selected: summary.id === selectedId,
2026
+ active: index === active,
2027
+ locale,
2028
+ onSelect
2029
+ },
2030
+ summary.id
2031
+ ))
2032
+ }
2033
+ );
2034
+ }
2035
+
2036
+ // src/shell/ConversationHeader.tsx
2037
+ import {
2038
+ Button as Button11,
2039
+ Menu as Menu2,
2040
+ MenuTrigger as MenuTrigger2,
2041
+ MenuPopover as MenuPopover2,
2042
+ MenuList as MenuList2,
2043
+ MenuItem as MenuItem2,
2044
+ Tab,
2045
+ TabList,
2046
+ Text as Text7,
2047
+ makeStyles as makeStyles22,
2048
+ tokens as tokens21
2049
+ } from "@fluentui/react-components";
2050
+ import { MoreHorizontalRegular as MoreHorizontalRegular2 } from "@fluentui/react-icons";
2051
+ import { jsx as jsx30, jsxs as jsxs22 } from "react/jsx-runtime";
2052
+ var useStyles21 = makeStyles22({
2053
+ root: {
2054
+ display: "flex",
2055
+ flexDirection: "column",
2056
+ gap: tokens21.spacingVerticalXS,
2057
+ padding: `${tokens21.spacingVerticalS} ${tokens21.spacingHorizontalM} 0`,
2058
+ borderBottom: `1px solid ${tokens21.colorNeutralStroke2}`,
2059
+ backgroundColor: tokens21.colorNeutralBackground1
2060
+ },
2061
+ top: { display: "flex", alignItems: "center", gap: tokens21.spacingHorizontalS },
2062
+ identity: { display: "flex", alignItems: "center", gap: tokens21.spacingHorizontalS, minWidth: 0 },
2063
+ titles: { display: "flex", flexDirection: "column", minWidth: 0 },
2064
+ subtitle: { color: tokens21.colorNeutralForeground3, fontSize: tokens21.fontSizeBase200 },
2065
+ spacer: { flex: 1 },
2066
+ tabRow: { display: "flex", alignItems: "flex-end", gap: tokens21.spacingHorizontalS }
2067
+ });
2068
+ function ConversationHeader(props) {
2069
+ const {
2070
+ title,
2071
+ subtitle,
2072
+ participant,
2073
+ presence,
2074
+ tabs,
2075
+ selectedTabId,
2076
+ onTabSelect,
2077
+ actions,
2078
+ trailing
2079
+ } = props;
2080
+ const styles = useStyles21();
2081
+ const inline = (actions ?? []).slice(0, 2);
2082
+ const overflow = (actions ?? []).slice(2);
2083
+ return /* @__PURE__ */ jsxs22("header", { className: styles.root, children: [
2084
+ /* @__PURE__ */ jsxs22("div", { className: styles.top, children: [
2085
+ /* @__PURE__ */ jsxs22("div", { className: styles.identity, children: [
2086
+ /* @__PURE__ */ jsx30(AgentAvatar, { participant, presence, size: 32, name: title }),
2087
+ /* @__PURE__ */ jsxs22("div", { className: styles.titles, children: [
2088
+ /* @__PURE__ */ jsx30(Text7, { weight: "semibold", truncate: true, wrap: false, children: title }),
2089
+ subtitle ? /* @__PURE__ */ jsx30("span", { className: styles.subtitle, children: subtitle }) : null
2090
+ ] })
2091
+ ] }),
2092
+ /* @__PURE__ */ jsx30("div", { className: styles.spacer }),
2093
+ trailing,
2094
+ inline.map((action) => /* @__PURE__ */ jsx30(
2095
+ Button11,
2096
+ {
2097
+ appearance: "subtle",
2098
+ size: "small",
2099
+ icon: action.icon,
2100
+ "aria-label": action.label,
2101
+ onClick: action.onClick
2102
+ },
2103
+ action.key
2104
+ )),
2105
+ overflow.length > 0 ? /* @__PURE__ */ jsxs22(Menu2, { children: [
2106
+ /* @__PURE__ */ jsx30(MenuTrigger2, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx30(
2107
+ Button11,
2108
+ {
2109
+ appearance: "subtle",
2110
+ size: "small",
2111
+ icon: /* @__PURE__ */ jsx30(MoreHorizontalRegular2, {}),
2112
+ "aria-label": `More actions, ${overflow.length} available`
2113
+ }
2114
+ ) }),
2115
+ /* @__PURE__ */ jsx30(MenuPopover2, { children: /* @__PURE__ */ jsx30(MenuList2, { children: overflow.map((action) => /* @__PURE__ */ jsx30(MenuItem2, { icon: action.icon, onClick: action.onClick, children: action.label }, action.key)) }) })
2116
+ ] }) : null
2117
+ ] }),
2118
+ tabs && tabs.length > 0 ? /* @__PURE__ */ jsx30("div", { className: styles.tabRow, children: /* @__PURE__ */ jsx30(
2119
+ TabList,
2120
+ {
2121
+ selectedValue: selectedTabId,
2122
+ onTabSelect: (_, data) => onTabSelect?.(data.value),
2123
+ size: "small",
2124
+ children: tabs.map((tab) => /* @__PURE__ */ jsx30(Tab, { value: tab.id, children: tab.label }, tab.id))
2125
+ }
2126
+ ) }) : null
2127
+ ] });
2128
+ }
2129
+
2130
+ // src/shell/RightPane.tsx
2131
+ import * as React16 from "react";
2132
+ import { Button as Button12, makeStyles as makeStyles23, tokens as tokens22, Text as Text8 } from "@fluentui/react-components";
2133
+ import { DismissRegular } from "@fluentui/react-icons";
2134
+ import { jsx as jsx31, jsxs as jsxs23 } from "react/jsx-runtime";
2135
+ var useStyles22 = makeStyles23({
2136
+ root: {
2137
+ display: "flex",
2138
+ flexDirection: "column",
2139
+ borderLeft: `1px solid ${tokens22.colorNeutralStroke2}`,
2140
+ backgroundColor: tokens22.colorNeutralBackground1,
2141
+ minHeight: 0
2142
+ },
2143
+ head: {
2144
+ display: "flex",
2145
+ alignItems: "center",
2146
+ justifyContent: "space-between",
2147
+ gap: tokens22.spacingHorizontalS,
2148
+ padding: tokens22.spacingVerticalS,
2149
+ borderBottom: `1px solid ${tokens22.colorNeutralStroke2}`
2150
+ },
2151
+ body: { flex: 1, overflowY: "auto", padding: tokens22.spacingVerticalM }
2152
+ });
2153
+ function RightPane({ open, title, onClose, children, width = 320 }) {
2154
+ const styles = useStyles22();
2155
+ const labelId = React16.useId();
2156
+ if (!open) return null;
2157
+ return /* @__PURE__ */ jsxs23("aside", { className: styles.root, style: { width }, "aria-labelledby": labelId, children: [
2158
+ /* @__PURE__ */ jsxs23("div", { className: styles.head, children: [
2159
+ /* @__PURE__ */ jsx31(Text8, { id: labelId, weight: "semibold", children: title }),
2160
+ /* @__PURE__ */ jsx31(
2161
+ Button12,
2162
+ {
2163
+ appearance: "subtle",
2164
+ size: "small",
2165
+ icon: /* @__PURE__ */ jsx31(DismissRegular, {}),
2166
+ onClick: onClose,
2167
+ "aria-label": `Close ${title}`
2168
+ }
2169
+ )
2170
+ ] }),
2171
+ /* @__PURE__ */ jsx31("div", { className: styles.body, children })
2172
+ ] });
2173
+ }
2174
+
2175
+ // src/shell/GlobalSearchBar.tsx
2176
+ import { SearchBox, makeStyles as makeStyles24 } from "@fluentui/react-components";
2177
+ import { jsx as jsx32 } from "react/jsx-runtime";
2178
+ var useStyles23 = makeStyles24({
2179
+ root: { width: "100%", maxWidth: "640px" }
2180
+ });
2181
+ function GlobalSearchBar({
2182
+ value,
2183
+ onChange,
2184
+ onSubmit,
2185
+ placeholder = "Search conversations"
2186
+ }) {
2187
+ const styles = useStyles23();
2188
+ return /* @__PURE__ */ jsx32(
2189
+ SearchBox,
2190
+ {
2191
+ className: styles.root,
2192
+ value,
2193
+ placeholder,
2194
+ "aria-label": placeholder,
2195
+ onChange: (_, data) => onChange(data.value),
2196
+ onKeyDown: (e) => {
2197
+ if (e.key === "Enter" && onSubmit) onSubmit(value);
2198
+ }
2199
+ }
2200
+ );
2201
+ }
2202
+
2203
+ // src/grounding/CitationChip.tsx
2204
+ import { Tooltip as Tooltip5, makeStyles as makeStyles25, tokens as tokens23 } from "@fluentui/react-components";
2205
+ import { jsx as jsx33 } from "react/jsx-runtime";
2206
+ var useStyles24 = makeStyles25({
2207
+ chip: {
2208
+ display: "inline-flex",
2209
+ alignItems: "center",
2210
+ justifyContent: "center",
2211
+ minWidth: "18px",
2212
+ height: "18px",
2213
+ padding: `0 ${tokens23.spacingHorizontalXXS}`,
2214
+ marginLeft: "2px",
2215
+ verticalAlign: "super",
2216
+ fontSize: tokens23.fontSizeBase100,
2217
+ lineHeight: "18px",
2218
+ borderRadius: tokens23.borderRadiusSmall,
2219
+ border: `1px solid ${tokens23.colorNeutralStroke2}`,
2220
+ backgroundColor: tokens23.colorNeutralBackground3,
2221
+ color: tokens23.colorNeutralForeground2,
2222
+ cursor: "pointer",
2223
+ ":hover": { backgroundColor: tokens23.colorNeutralBackground3Hover },
2224
+ ":focus-visible": { outline: `2px solid ${tokens23.colorStrokeFocus2}`, outlineOffset: "1px" }
2225
+ },
2226
+ inert: { cursor: "default" }
2227
+ });
2228
+ function CitationChip({ marker, source, onOpen }) {
2229
+ const styles = useStyles24();
2230
+ const label = source ? `Source ${marker}: ${source.title}` : `Source ${marker}`;
2231
+ if (!source || !onOpen) {
2232
+ return /* @__PURE__ */ jsx33("span", { className: `${styles.chip} ${styles.inert}`, "aria-label": label, children: marker });
2233
+ }
2234
+ return /* @__PURE__ */ jsx33(Tooltip5, { content: source.title, relationship: "description", withArrow: true, children: /* @__PURE__ */ jsx33("button", { type: "button", className: styles.chip, "aria-label": label, onClick: () => onOpen(source), children: marker }) });
2235
+ }
2236
+
2237
+ // src/grounding/CitationList.tsx
2238
+ import * as React17 from "react";
2239
+ import { makeStyles as makeStyles27, tokens as tokens25 } from "@fluentui/react-components";
2240
+
2241
+ // src/grounding/SourceCard.tsx
2242
+ import { Button as Button13, Text as Text9, makeStyles as makeStyles26, tokens as tokens24 } from "@fluentui/react-components";
2243
+ import {
2244
+ DocumentRegular,
2245
+ DatabaseRegular,
2246
+ GlobeRegular,
2247
+ BookRegular,
2248
+ DocumentBulletListRegular
2249
+ } from "@fluentui/react-icons";
2250
+ import { jsx as jsx34, jsxs as jsxs24 } from "react/jsx-runtime";
2251
+ var ICONS = {
2252
+ dataverse: DatabaseRegular,
2253
+ document: DocumentRegular,
2254
+ web: GlobeRegular,
2255
+ knowledge: BookRegular,
2256
+ other: DocumentBulletListRegular
2257
+ };
2258
+ var useStyles25 = makeStyles26({
2259
+ root: { gap: tokens24.spacingVerticalXXS },
2260
+ head: { display: "flex", alignItems: "center", gap: tokens24.spacingHorizontalXS },
2261
+ marker: { color: tokens24.colorNeutralForeground3, fontSize: tokens24.fontSizeBase200 },
2262
+ snippet: { color: tokens24.colorNeutralForeground2, fontSize: tokens24.fontSizeBase200 },
2263
+ meta: { display: "flex", alignItems: "center", gap: tokens24.spacingHorizontalS, marginTop: tokens24.spacingVerticalXXS }
2264
+ });
2265
+ function SourceCard({ source, marker, onOpen, showSnippet = true }) {
2266
+ const styles = useStyles25();
2267
+ const Icon = ICONS[source.kind] ?? ICONS.other;
2268
+ return /* @__PURE__ */ jsxs24(CardSurface, { className: styles.root, children: [
2269
+ /* @__PURE__ */ jsxs24("div", { className: styles.head, children: [
2270
+ /* @__PURE__ */ jsx34(Icon, { "aria-hidden": "true" }),
2271
+ marker ? /* @__PURE__ */ jsxs24("span", { className: styles.marker, children: [
2272
+ "[",
2273
+ marker,
2274
+ "]"
2275
+ ] }) : null,
2276
+ /* @__PURE__ */ jsx34(Text9, { weight: "semibold", children: source.title })
2277
+ ] }),
2278
+ showSnippet && source.snippet ? /* @__PURE__ */ jsx34("span", { className: styles.snippet, children: source.snippet }) : null,
2279
+ /* @__PURE__ */ jsxs24("div", { className: styles.meta, children: [
2280
+ onOpen && (source.recordId || source.url) ? /* @__PURE__ */ jsx34(Button13, { size: "small", appearance: "subtle", onClick: () => onOpen(source), children: source.recordId ? "Open record" : "Open" }) : null,
2281
+ /* @__PURE__ */ jsx34("span", { className: styles.snippet, children: source.score === void 0 ? "Relevance not reported" : `Relevance ${Math.round(source.score * 100)}%` })
2282
+ ] })
2283
+ ] });
2284
+ }
2285
+
2286
+ // src/grounding/CitationList.tsx
2287
+ import { jsx as jsx35 } from "react/jsx-runtime";
2288
+ var useStyles26 = makeStyles27({
2289
+ list: { display: "flex", flexDirection: "column", gap: tokens25.spacingVerticalXS, listStyle: "none", margin: 0, padding: 0 }
2290
+ });
2291
+ function CitationList({
2292
+ text,
2293
+ sources,
2294
+ onOpenSource,
2295
+ defaultOpen = false
2296
+ }) {
2297
+ const styles = useStyles26();
2298
+ const id = React17.useId();
2299
+ const { used, citations } = React17.useMemo(() => renumberCitations(text, sources), [text, sources]);
2300
+ if (used.length === 0) return null;
2301
+ const markerFor = (sourceId) => citations.find((c) => c.sourceId === sourceId)?.marker;
2302
+ return /* @__PURE__ */ jsx35(
2303
+ Disclosure,
2304
+ {
2305
+ id,
2306
+ defaultOpen,
2307
+ summary: `${used.length} source${used.length === 1 ? "" : "s"}`,
2308
+ children: /* @__PURE__ */ jsx35("ul", { className: styles.list, children: used.map((source) => /* @__PURE__ */ jsx35("li", { children: /* @__PURE__ */ jsx35(SourceCard, { source, marker: markerFor(source.id), onOpen: onOpenSource }) }, source.id)) })
2309
+ }
2310
+ );
2311
+ }
2312
+
2313
+ // src/grounding/AnswerWithCitations.tsx
2314
+ import * as React18 from "react";
2315
+ import { makeStyles as makeStyles28 } from "@fluentui/react-components";
2316
+ import { jsx as jsx36 } from "react/jsx-runtime";
2317
+ var useStyles27 = makeStyles28({ root: { whiteSpace: "pre-wrap", wordBreak: "break-word" } });
2318
+ function AnswerWithCitations({
2319
+ text,
2320
+ citations = [],
2321
+ sources = [],
2322
+ onOpenSource
2323
+ }) {
2324
+ const styles = useStyles27();
2325
+ const segments = React18.useMemo(() => splitTextByCitations(text, citations), [text, citations]);
2326
+ const byId = React18.useMemo(() => new Map(sources.map((s) => [s.id, s])), [sources]);
2327
+ return /* @__PURE__ */ jsx36("span", { className: styles.root, children: segments.map(
2328
+ (segment, i) => segment.kind === "text" ? /* @__PURE__ */ jsx36(React18.Fragment, { children: segment.text }, i) : /* @__PURE__ */ jsx36(
2329
+ CitationChip,
2330
+ {
2331
+ marker: segment.marker ?? "",
2332
+ source: segment.sourceId ? byId.get(segment.sourceId) : void 0,
2333
+ onOpen: onOpenSource
2334
+ },
2335
+ i
2336
+ )
2337
+ ) });
2338
+ }
2339
+
2340
+ // src/grounding/GroundingPanel.tsx
2341
+ import * as React19 from "react";
2342
+ import { Text as Text10, makeStyles as makeStyles29, tokens as tokens26 } from "@fluentui/react-components";
2343
+ import { jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
2344
+ var useStyles28 = makeStyles29({
2345
+ grid: {
2346
+ display: "grid",
2347
+ gridTemplateColumns: "minmax(96px, max-content) 1fr",
2348
+ gap: `${tokens26.spacingVerticalXXS} ${tokens26.spacingHorizontalM}`,
2349
+ margin: 0,
2350
+ fontSize: tokens26.fontSizeBase200
2351
+ },
2352
+ key: { color: tokens26.colorNeutralForeground3, margin: 0 },
2353
+ value: { margin: 0, wordBreak: "break-word", fontFamily: tokens26.fontFamilyMonospace },
2354
+ empty: { fontSize: tokens26.fontSizeBase200, color: tokens26.colorNeutralForeground3 }
2355
+ });
2356
+ function render(value) {
2357
+ if (value === null || value === void 0) return "\u2014";
2358
+ if (typeof value === "string") return value;
2359
+ return JSON.stringify(value);
2360
+ }
2361
+ function GroundingPanel({
2362
+ grounding,
2363
+ title = "What this answer used",
2364
+ defaultOpen = false
2365
+ }) {
2366
+ const styles = useStyles28();
2367
+ const id = React19.useId();
2368
+ const entries = Object.entries(grounding ?? {});
2369
+ if (entries.length === 0) return null;
2370
+ return /* @__PURE__ */ jsxs25(Disclosure, { id, defaultOpen, summary: title, children: [
2371
+ /* @__PURE__ */ jsx37("dl", { className: styles.grid, children: entries.map(([key, value]) => /* @__PURE__ */ jsxs25(React19.Fragment, { children: [
2372
+ /* @__PURE__ */ jsx37("dt", { className: styles.key, children: key }),
2373
+ /* @__PURE__ */ jsx37("dd", { className: styles.value, children: render(value) })
2374
+ ] }, key)) }),
2375
+ /* @__PURE__ */ jsx37(Text10, { as: "p", className: styles.empty, children: "These facts were supplied by the app, not produced by the model." })
2376
+ ] });
2377
+ }
2378
+
2379
+ // src/grounding/ProviderAttribution.tsx
2380
+ import { makeStyles as makeStyles30, tokens as tokens27 } from "@fluentui/react-components";
2381
+ import { ShieldErrorRegular as ShieldErrorRegular2 } from "@fluentui/react-icons";
2382
+ import { jsx as jsx38, jsxs as jsxs26 } from "react/jsx-runtime";
2383
+ var useStyles29 = makeStyles30({
2384
+ root: {
2385
+ display: "flex",
2386
+ alignItems: "center",
2387
+ gap: tokens27.spacingHorizontalXXS,
2388
+ fontSize: tokens27.fontSizeBase100,
2389
+ color: tokens27.colorNeutralForeground3
2390
+ },
2391
+ guarded: { color: tokens27.colorPaletteDarkOrangeForeground1 }
2392
+ });
2393
+ function ProviderAttribution({ provider, model, guarded }) {
2394
+ const styles = useStyles29();
2395
+ if (!provider && !model && !guarded) return null;
2396
+ const who = [provider, model].filter(Boolean).join(" \xB7 ");
2397
+ if (guarded) {
2398
+ return /* @__PURE__ */ jsxs26("span", { className: `${styles.root} ${styles.guarded}`, children: [
2399
+ /* @__PURE__ */ jsx38(ShieldErrorRegular2, { "aria-hidden": "true" }),
2400
+ "Withheld and replaced with safe text",
2401
+ who ? ` \u2014 not ${who}'s words` : ""
2402
+ ] });
2403
+ }
2404
+ return /* @__PURE__ */ jsxs26("span", { className: styles.root, children: [
2405
+ "Answered by ",
2406
+ who || "the agent"
2407
+ ] });
2408
+ }
2409
+ function messageAttribution(message) {
2410
+ return { provider: message.provider, model: message.usage?.model, guarded: message.guarded };
2411
+ }
2412
+
2413
+ // src/actions/ActionCard.tsx
2414
+ import * as React20 from "react";
2415
+ import { Text as Text11, makeStyles as makeStyles31, tokens as tokens28 } from "@fluentui/react-components";
2416
+ import { jsx as jsx39, jsxs as jsxs27 } from "react/jsx-runtime";
2417
+ var useStyles30 = makeStyles31({
2418
+ head: { display: "flex", alignItems: "center", gap: tokens28.spacingHorizontalXS },
2419
+ description: { color: tokens28.colorNeutralForeground2, fontSize: tokens28.fontSizeBase200 },
2420
+ footer: { display: "flex", gap: tokens28.spacingHorizontalS, justifyContent: "flex-end", marginTop: tokens28.spacingVerticalXS }
2421
+ });
2422
+ function ActionCard({ title, description, icon, pending, children, footer }) {
2423
+ const styles = useStyles30();
2424
+ return /* @__PURE__ */ jsxs27(CardSurface, { emphasis: pending ? "attention" : "none", children: [
2425
+ /* @__PURE__ */ jsxs27("div", { className: styles.head, children: [
2426
+ icon ? React20.cloneElement(icon, { "aria-hidden": "true" }) : null,
2427
+ /* @__PURE__ */ jsx39(Text11, { weight: "semibold", children: title })
2428
+ ] }),
2429
+ description ? /* @__PURE__ */ jsx39("span", { className: styles.description, children: description }) : null,
2430
+ children,
2431
+ footer ? /* @__PURE__ */ jsx39("div", { className: styles.footer, children: footer }) : null
2432
+ ] });
2433
+ }
2434
+
2435
+ // src/actions/ApprovalCard.tsx
2436
+ import * as React22 from "react";
2437
+ import { Button as Button15, Spinner, makeStyles as makeStyles34, tokens as tokens31 } from "@fluentui/react-components";
2438
+ import { CheckmarkRegular, DismissRegular as DismissRegular2, EditRegular } from "@fluentui/react-icons";
2439
+
2440
+ // src/actions/ParameterReview.tsx
2441
+ import * as React21 from "react";
2442
+ import { Input, makeStyles as makeStyles32, tokens as tokens29, Text as Text12 } from "@fluentui/react-components";
2443
+ import { jsx as jsx40, jsxs as jsxs28 } from "react/jsx-runtime";
2444
+ var useStyles31 = makeStyles32({
2445
+ grid: {
2446
+ display: "grid",
2447
+ gridTemplateColumns: "minmax(110px, max-content) 1fr",
2448
+ gap: `${tokens29.spacingVerticalXS} ${tokens29.spacingHorizontalM}`,
2449
+ alignItems: "center",
2450
+ margin: 0
2451
+ },
2452
+ key: { margin: 0, color: tokens29.colorNeutralForeground2, fontSize: tokens29.fontSizeBase200 },
2453
+ value: { margin: 0 },
2454
+ raw: {
2455
+ whiteSpace: "pre-wrap",
2456
+ wordBreak: "break-all",
2457
+ fontFamily: tokens29.fontFamilyMonospace,
2458
+ fontSize: tokens29.fontSizeBase200,
2459
+ color: tokens29.colorNeutralForeground2
2460
+ }
2461
+ });
2462
+ function ParameterReview({ argumentsJson, readOnly = true, onChange }) {
2463
+ const styles = useStyles31();
2464
+ const parsed = React21.useMemo(() => {
2465
+ try {
2466
+ const value = JSON.parse(argumentsJson);
2467
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
2468
+ } catch {
2469
+ return null;
2470
+ }
2471
+ }, [argumentsJson]);
2472
+ if (!parsed) {
2473
+ return /* @__PURE__ */ jsxs28("div", { children: [
2474
+ /* @__PURE__ */ jsx40(Text12, { size: 200, children: "Arguments (unparsed)" }),
2475
+ /* @__PURE__ */ jsx40("pre", { className: styles.raw, children: argumentsJson || "(empty)" })
2476
+ ] });
2477
+ }
2478
+ const set = (key, next) => {
2479
+ if (!onChange) return;
2480
+ onChange(JSON.stringify({ ...parsed, [key]: next }));
2481
+ };
2482
+ return /* @__PURE__ */ jsx40("dl", { className: styles.grid, children: Object.entries(parsed).map(([key, value]) => /* @__PURE__ */ jsxs28(React21.Fragment, { children: [
2483
+ /* @__PURE__ */ jsx40("dt", { className: styles.key, children: key }),
2484
+ /* @__PURE__ */ jsx40("dd", { className: styles.value, children: readOnly ? /* @__PURE__ */ jsx40("span", { className: styles.raw, children: typeof value === "string" ? value : JSON.stringify(value) }) : /* @__PURE__ */ jsx40(
2485
+ Input,
2486
+ {
2487
+ size: "small",
2488
+ "aria-label": key,
2489
+ value: typeof value === "string" ? value : JSON.stringify(value),
2490
+ onChange: (_, data) => set(key, data.value)
2491
+ }
2492
+ ) })
2493
+ ] }, key)) });
2494
+ }
2495
+
2496
+ // src/actions/ActionResult.tsx
2497
+ import { MessageBar as MessageBar4, MessageBarBody as MessageBarBody4, MessageBarTitle as MessageBarTitle4, MessageBarActions as MessageBarActions4, Button as Button14, makeStyles as makeStyles33, tokens as tokens30 } from "@fluentui/react-components";
2498
+ import { jsx as jsx41, jsxs as jsxs29 } from "react/jsx-runtime";
2499
+ var INTENT2 = { succeeded: "success", failed: "error", denied: "warning" };
2500
+ var TITLE = { succeeded: "Done", failed: "Failed", denied: "Not approved" };
2501
+ var useStyles32 = makeStyles33({
2502
+ id: { display: "block", marginTop: tokens30.spacingVerticalXXS, fontSize: tokens30.fontSizeBase100, color: tokens30.colorNeutralForeground3, fontFamily: tokens30.fontFamilyMonospace }
2503
+ });
2504
+ function ActionResult({ status, message, correlationId, onRetry }) {
2505
+ const styles = useStyles32();
2506
+ return /* @__PURE__ */ jsxs29(MessageBar4, { intent: INTENT2[status], children: [
2507
+ /* @__PURE__ */ jsxs29(MessageBarBody4, { children: [
2508
+ /* @__PURE__ */ jsx41(MessageBarTitle4, { children: TITLE[status] }),
2509
+ message,
2510
+ correlationId ? /* @__PURE__ */ jsxs29("span", { className: styles.id, children: [
2511
+ "Correlation id: ",
2512
+ correlationId
2513
+ ] }) : null
2514
+ ] }),
2515
+ status === "failed" && onRetry ? /* @__PURE__ */ jsx41(MessageBarActions4, { children: /* @__PURE__ */ jsx41(Button14, { size: "small", onClick: onRetry, children: "Try again" }) }) : null
2516
+ ] });
2517
+ }
2518
+
2519
+ // src/actions/ApprovalCard.tsx
2520
+ import { Fragment as Fragment8, jsx as jsx42, jsxs as jsxs30 } from "react/jsx-runtime";
2521
+ var useStyles33 = makeStyles34({
2522
+ busy: { display: "flex", alignItems: "center", gap: tokens31.spacingHorizontalXS, fontSize: tokens31.fontSizeBase200 }
2523
+ });
2524
+ function ApprovalCard({
2525
+ toolCall,
2526
+ onApprove,
2527
+ onDeny,
2528
+ allowEdit = false,
2529
+ busy = false
2530
+ }) {
2531
+ const styles = useStyles33();
2532
+ const [args, setArgs] = React22.useState(toolCall.argumentsJson);
2533
+ const [editing, setEditing] = React22.useState(false);
2534
+ const [decided, setDecided] = React22.useState(false);
2535
+ const settled = toolCall.status === "succeeded" || toolCall.status === "failed" || toolCall.status === "denied";
2536
+ if (settled) {
2537
+ return /* @__PURE__ */ jsxs30(ActionCard, { title: toolCall.name, description: "Tool call", children: [
2538
+ /* @__PURE__ */ jsx42(ParameterReview, { argumentsJson: toolCall.argumentsJson }),
2539
+ /* @__PURE__ */ jsx42(
2540
+ ActionResult,
2541
+ {
2542
+ status: toolCall.status === "succeeded" ? "succeeded" : toolCall.status === "denied" ? "denied" : "failed",
2543
+ message: toolCall.resultText ?? toolCall.failure?.message ?? "Completed.",
2544
+ correlationId: toolCall.failure?.correlationId
2545
+ }
2546
+ )
2547
+ ] });
2548
+ }
2549
+ const locked = decided || busy || toolCall.status === "running";
2550
+ const approve = () => {
2551
+ if (locked) return;
2552
+ setDecided(true);
2553
+ onApprove(toolCall, args);
2554
+ };
2555
+ const deny = () => {
2556
+ if (locked) return;
2557
+ setDecided(true);
2558
+ onDeny(toolCall);
2559
+ };
2560
+ return /* @__PURE__ */ jsx42(
2561
+ ActionCard,
2562
+ {
2563
+ title: toolCall.name,
2564
+ description: "The agent is asking to run this. Check the details before approving.",
2565
+ pending: !locked,
2566
+ footer: locked ? /* @__PURE__ */ jsxs30("span", { className: styles.busy, children: [
2567
+ /* @__PURE__ */ jsx42(Spinner, { size: "tiny" }),
2568
+ " Submitting\u2026"
2569
+ ] }) : /* @__PURE__ */ jsxs30(Fragment8, { children: [
2570
+ allowEdit ? /* @__PURE__ */ jsx42(Button15, { size: "small", icon: /* @__PURE__ */ jsx42(EditRegular, {}), onClick: () => setEditing((v) => !v), children: editing ? "Done editing" : "Edit" }) : null,
2571
+ /* @__PURE__ */ jsx42(Button15, { size: "small", icon: /* @__PURE__ */ jsx42(DismissRegular2, {}), onClick: deny, disabled: locked, children: "Deny" }),
2572
+ /* @__PURE__ */ jsx42(Button15, { size: "small", appearance: "primary", icon: /* @__PURE__ */ jsx42(CheckmarkRegular, {}), onClick: approve, disabled: locked, children: "Approve" })
2573
+ ] }),
2574
+ children: /* @__PURE__ */ jsx42(ParameterReview, { argumentsJson: args, readOnly: !editing, onChange: setArgs })
2575
+ }
2576
+ );
2577
+ }
2578
+
2579
+ // src/actions/ToolCallCard.tsx
2580
+ import * as React23 from "react";
2581
+ import { Badge as Badge3, Spinner as Spinner2, makeStyles as makeStyles35, tokens as tokens32 } from "@fluentui/react-components";
2582
+ import { jsx as jsx43, jsxs as jsxs31 } from "react/jsx-runtime";
2583
+ var LABEL = {
2584
+ proposed: "Proposed",
2585
+ running: "Running",
2586
+ succeeded: "Succeeded",
2587
+ failed: "Failed",
2588
+ denied: "Denied"
2589
+ };
2590
+ var COLOUR = {
2591
+ proposed: "informative",
2592
+ running: "informative",
2593
+ succeeded: "success",
2594
+ failed: "danger",
2595
+ denied: "warning"
2596
+ };
2597
+ var useStyles34 = makeStyles35({
2598
+ head: { display: "flex", alignItems: "center", gap: tokens32.spacingHorizontalXS },
2599
+ name: { fontFamily: tokens32.fontFamilyMonospace, fontSize: tokens32.fontSizeBase200 },
2600
+ result: {
2601
+ whiteSpace: "pre-wrap",
2602
+ wordBreak: "break-all",
2603
+ fontFamily: tokens32.fontFamilyMonospace,
2604
+ fontSize: tokens32.fontSizeBase200,
2605
+ marginTop: tokens32.spacingVerticalXS
2606
+ }
2607
+ });
2608
+ function ToolCallCard({ toolCall, defaultExpanded = false }) {
2609
+ const styles = useStyles34();
2610
+ const id = React23.useId();
2611
+ return /* @__PURE__ */ jsx43(CardSurface, { children: /* @__PURE__ */ jsxs31(
2612
+ Disclosure,
2613
+ {
2614
+ id,
2615
+ defaultOpen: defaultExpanded,
2616
+ summary: /* @__PURE__ */ jsxs31("span", { className: styles.head, children: [
2617
+ /* @__PURE__ */ jsx43("span", { className: styles.name, children: toolCall.name }),
2618
+ /* @__PURE__ */ jsx43(Badge3, { appearance: "tint", color: COLOUR[toolCall.status], children: LABEL[toolCall.status] }),
2619
+ toolCall.status === "running" ? /* @__PURE__ */ jsx43(Spinner2, { size: "extra-tiny" }) : null
2620
+ ] }),
2621
+ children: [
2622
+ /* @__PURE__ */ jsx43(ParameterReview, { argumentsJson: toolCall.argumentsJson }),
2623
+ toolCall.resultText ? /* @__PURE__ */ jsx43("div", { className: styles.result, children: toolCall.resultText }) : null,
2624
+ toolCall.failure ? /* @__PURE__ */ jsx43("div", { className: styles.result, children: toolCall.failure.message }) : null
2625
+ ]
2626
+ }
2627
+ ) });
2628
+ }
2629
+
2630
+ // src/host/AgentKitHostProvider.tsx
2631
+ import * as React24 from "react";
2632
+ import { FluentProvider, IdPrefixProvider } from "@fluentui/react-components";
2633
+
2634
+ // src/theme/index.ts
2635
+ import {
2636
+ createDynamicsV9Theme,
2637
+ createDynamicsV9DarkTheme,
2638
+ dynamicsBrandRamp
2639
+ } from "@dataverse-kit/fluent-theme";
2640
+ import { typography, radius } from "@dataverse-kit/design-tokens";
2641
+ function applyAgentOverrides(base) {
2642
+ return {
2643
+ ...base,
2644
+ // Fluent's web theme is close to CE but does not pin the Web variant of Segoe.
2645
+ fontFamilyBase: typography.fontFamily,
2646
+ // CE is squarer than Fluent web's default. Bubbles opt back up to borderRadiusLarge.
2647
+ borderRadiusMedium: radius.card
2648
+ };
2649
+ }
2650
+ function createAgentTheme(brand = dynamicsBrandRamp) {
2651
+ return applyAgentOverrides(createDynamicsV9Theme(brand));
2652
+ }
2653
+ function createAgentDarkTheme(brand = dynamicsBrandRamp) {
2654
+ return applyAgentOverrides(createDynamicsV9DarkTheme(brand));
2655
+ }
2656
+ var agentTheme = createAgentTheme();
2657
+ var agentDarkTheme = createAgentDarkTheme();
2658
+ function resolveHostTheme(input = {}) {
2659
+ if (input.theme) return input.theme;
2660
+ if (input.hostTheme) return input.hostTheme;
2661
+ return input.isDarkTheme ? agentDarkTheme : agentTheme;
2662
+ }
2663
+
2664
+ // src/host/idPrefix.ts
2665
+ var nextAgentIdPrefix = createIdPrefixCounter("dvkak");
2666
+
2667
+ // src/host/AgentKitHostProvider.tsx
2668
+ import { jsx as jsx44 } from "react/jsx-runtime";
2669
+ function AgentKitHostProvider(props) {
2670
+ const { children, theme, hostTheme, isDarkTheme, targetDocument, className, style } = props;
2671
+ const [generated] = React24.useState(nextAgentIdPrefix);
2672
+ const idPrefix = props.idPrefix ?? generated;
2673
+ const resolved = React24.useMemo(
2674
+ () => resolveHostTheme({ theme, hostTheme, isDarkTheme }),
2675
+ [theme, hostTheme, isDarkTheme]
2676
+ );
2677
+ return /* @__PURE__ */ jsx44(IdPrefixProvider, { value: idPrefix, children: /* @__PURE__ */ jsx44(
2678
+ FluentProvider,
2679
+ {
2680
+ theme: resolved,
2681
+ targetDocument,
2682
+ className,
2683
+ style,
2684
+ children
2685
+ }
2686
+ ) });
2687
+ }
2688
+
2689
+ // src/services/scenarios.ts
2690
+ var SOURCES = [
2691
+ { id: "src-acct", title: "Contoso Ltd", kind: "dataverse", entityLogicalName: "account", recordId: "11111111-1111-1111-1111-111111111111", snippet: "Annual revenue \xA34.2m, 38 employees." },
2692
+ { id: "src-case", title: "Case 4417 \u2014 invoice mismatch", kind: "dataverse", entityLogicalName: "incident", recordId: "22222222-2222-2222-2222-222222222222", snippet: "Raised 12 Mar, priority High." },
2693
+ { id: "src-kb", title: "KB-208: credit note policy", kind: "knowledge", url: "https://example.invalid/kb/208", snippet: "Credit notes above \xA35,000 require finance approval.", score: 0.82 },
2694
+ { id: "src-doc", title: "Statement-Mar.pdf", kind: "document", snippet: "Page 3 lists the disputed line." },
2695
+ { id: "src-web", title: "Companies House filing", kind: "web", url: "https://example.invalid/ch" }
2696
+ ];
2697
+ var mockScenarios = {
2698
+ happy: {
2699
+ text: "Contoso has three open cases. The oldest, case 4417, has been waiting eleven days for a credit note.",
2700
+ status: "succeeded",
2701
+ suggestions: ["Draft a credit note", "Show all three cases", "Who owns case 4417?"]
2702
+ },
2703
+ // ★ FIVE sources, but the text cites only [3] and [1] — and out of order. The panel must
2704
+ // show TWO, renumbered 1 and 2 in first-appearance order. A fixture where every source
2705
+ // is cited cannot tell a correct implementation from one that lists everything it was given.
2706
+ grounded: {
2707
+ text: "Credit notes over \xA35,000 need finance approval [3], and case 4417 is for \xA36,200 [1]. So this one needs sign-off.",
2708
+ status: "succeeded",
2709
+ sources: SOURCES
2710
+ },
2711
+ toolApproval: {
2712
+ text: "I can raise the credit note for you \u2014 approve the details below and I will submit it.",
2713
+ status: "succeeded",
2714
+ toolCall: {
2715
+ id: "call-1",
2716
+ name: "create_credit_note",
2717
+ argumentsJson: '{"accountId":"11111111-1111-1111-1111-111111111111","amount":6200,"currency":"GBP","reason":"Invoice mismatch, case 4417"}',
2718
+ status: "proposed",
2719
+ requiresApproval: true
2720
+ }
2721
+ },
2722
+ toolAuto: {
2723
+ text: "I checked the balance for you.",
2724
+ status: "succeeded",
2725
+ toolCall: { id: "call-2", name: "get_account_balance", argumentsJson: '{"accountId":"1111"}', status: "running" },
2726
+ autoToolResult: '{"balance":6200,"currency":"GBP"}'
2727
+ },
2728
+ // ★ blocked and refused MUST look different on screen: a content filter fired, versus the
2729
+ // model declining. They need different fixes, so they are different states.
2730
+ blocked: {
2731
+ text: "I can\u2019t help with that request.",
2732
+ status: "blocked",
2733
+ guarded: true,
2734
+ failureMessage: "The response was withheld by the content filter.",
2735
+ failureCode: "content_filter",
2736
+ recoverable: false
2737
+ },
2738
+ refused: {
2739
+ text: "I don\u2019t have enough information to answer that safely. Could you say which account you mean?",
2740
+ status: "refused",
2741
+ failureMessage: "The agent declined to answer.",
2742
+ recoverable: true
2743
+ },
2744
+ error: {
2745
+ text: "",
2746
+ status: "error",
2747
+ failureMessage: "The agent is rate limited. Try again shortly.",
2748
+ failureCode: "rate_limited",
2749
+ recoverable: true,
2750
+ retryAfterMs: 4e3
2751
+ },
2752
+ notProvisioned: {
2753
+ text: "",
2754
+ status: "notProvisioned",
2755
+ failureMessage: "No Foundry project is configured for this environment.",
2756
+ failureCode: "not_provisioned",
2757
+ recoverable: false,
2758
+ unavailable: true
2759
+ },
2760
+ cancelled: {
2761
+ text: "Let me pull the last six months of activity for Contoso and work through it case by case, starting with the oldest\u2026",
2762
+ status: "cancelled",
2763
+ failureMessage: "Stopped."
2764
+ },
2765
+ // The fixture the autoscroll and live-region gates run against: enough deltas that
2766
+ // "once, not per token" is a meaningful distinction.
2767
+ longAnswer: {
2768
+ text: Array.from({ length: 200 }, (_, i) => `word${i + 1}`).join(" "),
2769
+ status: "succeeded"
2770
+ },
2771
+ throws: {
2772
+ text: "partial\u2026",
2773
+ status: "error",
2774
+ throwsMidStream: true,
2775
+ failureMessage: "never reached \u2014 the iterator throws instead"
2776
+ }
2777
+ };
2778
+
2779
+ // src/services/MockAgentService.ts
2780
+ var realScheduler = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2781
+ var immediateScheduler = () => Promise.resolve();
2782
+ function mulberry32(seed) {
2783
+ let a = seed >>> 0;
2784
+ return () => {
2785
+ a = a + 1831565813 >>> 0;
2786
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
2787
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
2788
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
2789
+ };
2790
+ }
2791
+ var MockAgentService = class {
2792
+ constructor(options = {}) {
2793
+ this.name = "mock";
2794
+ this.scenarioName = options.scenario ?? "happy";
2795
+ this.scenario = mockScenarios[this.scenarioName];
2796
+ this.scheduler = options.scheduler ?? realScheduler;
2797
+ this.firstTokenMs = options.firstTokenMs ?? 400;
2798
+ this.chunkMs = options.chunkMs ?? 28;
2799
+ this.rand = mulberry32(options.seed ?? 1);
2800
+ this.availabilityOverride = options.availability;
2801
+ }
2802
+ async isAvailable() {
2803
+ if (this.availabilityOverride) return this.availabilityOverride;
2804
+ if (this.scenario.unavailable) {
2805
+ return {
2806
+ available: false,
2807
+ status: "notProvisioned",
2808
+ reason: this.scenario.failureMessage ?? "Not provisioned."
2809
+ };
2810
+ }
2811
+ return {
2812
+ available: true,
2813
+ capabilities: { streaming: true, tools: true, citations: true, attachments: true, cancel: true }
2814
+ };
2815
+ }
2816
+ /**
2817
+ * A prompt can select a scenario (`/blocked`, `/tool`, `/error`), so one story can walk
2818
+ * every state without remounting.
2819
+ */
2820
+ resolveScenario(promptText) {
2821
+ const slash = promptText.trim().match(/^\/(\w+)/)?.[1];
2822
+ if (slash && slash in mockScenarios) {
2823
+ return { name: slash, scenario: mockScenarios[slash] };
2824
+ }
2825
+ return { name: this.scenarioName, scenario: this.scenario };
2826
+ }
2827
+ async *send(turn) {
2828
+ const { scenario } = this.resolveScenario(turn.text);
2829
+ const messageId = `mock-${Math.floor(this.rand() * 1e9).toString(36)}`;
2830
+ const turnId = `turn-${messageId}`;
2831
+ const provider = this.name;
2832
+ const finish = (result) => ({
2833
+ type: "turn_end",
2834
+ turnId,
2835
+ messageId,
2836
+ result: { ...result, provider }
2837
+ });
2838
+ yield { type: "turn_start", turnId, messageId, provider, model: "mock-1" };
2839
+ if (turn.cancelSignal?.aborted) {
2840
+ yield finish({ status: "cancelled", text: "", failure: { status: "cancelled", message: "Stopped." } });
2841
+ return;
2842
+ }
2843
+ if (scenario.status === "notProvisioned" || scenario.status === "error" && !scenario.text) {
2844
+ await this.scheduler(this.firstTokenMs);
2845
+ yield finish({
2846
+ status: scenario.status,
2847
+ text: "",
2848
+ failure: {
2849
+ status: scenario.status,
2850
+ message: scenario.failureMessage ?? "Failed.",
2851
+ code: scenario.failureCode,
2852
+ recoverable: scenario.recoverable,
2853
+ retryAfterMs: scenario.retryAfterMs
2854
+ }
2855
+ });
2856
+ return;
2857
+ }
2858
+ if (scenario.sources) {
2859
+ yield { type: "status", messageId, label: "Searching Dataverse\u2026" };
2860
+ await this.scheduler(this.firstTokenMs);
2861
+ yield { type: "sources", messageId, sources: scenario.sources };
2862
+ }
2863
+ if (scenario.thinking) {
2864
+ yield { type: "thinking_delta", messageId, text: scenario.thinking };
2865
+ }
2866
+ if (scenario.toolCall) {
2867
+ yield { type: "tool_call", messageId, call: scenario.toolCall };
2868
+ if (scenario.autoToolResult !== void 0) {
2869
+ await this.scheduler(this.chunkMs);
2870
+ yield {
2871
+ type: "tool_result",
2872
+ messageId,
2873
+ callId: scenario.toolCall.id,
2874
+ status: "succeeded",
2875
+ resultText: scenario.autoToolResult
2876
+ };
2877
+ }
2878
+ }
2879
+ const chunks = scenario.chunks ?? scenario.text.split(/(?<=\s)/);
2880
+ await this.scheduler(this.firstTokenMs);
2881
+ let emitted = "";
2882
+ for (const [i, chunk] of chunks.entries()) {
2883
+ if (turn.cancelSignal?.aborted) {
2884
+ yield finish({
2885
+ status: "cancelled",
2886
+ text: emitted,
2887
+ failure: { status: "cancelled", message: "Stopped." }
2888
+ });
2889
+ return;
2890
+ }
2891
+ if (scenario.throwsMidStream && i === Math.floor(chunks.length / 2)) {
2892
+ throw new Error("mock transport exploded mid-stream");
2893
+ }
2894
+ emitted += chunk;
2895
+ yield { type: "text_delta", messageId, text: chunk };
2896
+ await this.scheduler(this.chunkMs);
2897
+ }
2898
+ if (scenario.suggestions) {
2899
+ yield { type: "suggestions", messageId, prompts: scenario.suggestions };
2900
+ }
2901
+ const failed = scenario.status !== "succeeded";
2902
+ yield finish({
2903
+ status: scenario.status,
2904
+ text: emitted,
2905
+ guarded: scenario.guarded,
2906
+ usage: {
2907
+ inputTokens: 128,
2908
+ outputTokens: chunks.length,
2909
+ costUsd: Number((chunks.length * 2e-6).toFixed(6)),
2910
+ durationMs: this.firstTokenMs + chunks.length * this.chunkMs,
2911
+ model: "mock-1"
2912
+ },
2913
+ failure: failed ? {
2914
+ status: scenario.status,
2915
+ message: scenario.failureMessage ?? "Failed.",
2916
+ code: scenario.failureCode,
2917
+ recoverable: scenario.recoverable,
2918
+ retryAfterMs: scenario.retryAfterMs
2919
+ } : void 0
2920
+ });
2921
+ }
2922
+ };
2923
+
2924
+ // src/services/NullAgentService.ts
2925
+ var NullAgentService = class {
2926
+ constructor() {
2927
+ this.name = "none";
2928
+ }
2929
+ async isAvailable() {
2930
+ return {
2931
+ available: false,
2932
+ status: "notProvisioned",
2933
+ reason: "No agent provider is configured for this host."
2934
+ };
2935
+ }
2936
+ async *send(turn) {
2937
+ const messageId = `null-${Date.now()}`;
2938
+ const turnId = `turn-${messageId}`;
2939
+ void turn;
2940
+ yield { type: "turn_start", turnId, messageId, provider: this.name };
2941
+ yield {
2942
+ type: "turn_end",
2943
+ turnId,
2944
+ messageId,
2945
+ result: {
2946
+ status: "notProvisioned",
2947
+ text: "",
2948
+ provider: this.name,
2949
+ failure: {
2950
+ status: "notProvisioned",
2951
+ message: "No agent provider is configured for this host.",
2952
+ code: "not_provisioned",
2953
+ recoverable: false
2954
+ }
2955
+ }
2956
+ };
2957
+ }
2958
+ };
2959
+
2960
+ // src/services/createAgentService.ts
2961
+ function decide(config) {
2962
+ switch (config.provider) {
2963
+ case "custom":
2964
+ return config.factory ? { provider: config.factory(), reason: "explicit factory (provider: custom)" } : { provider: new NullAgentService(), reason: "provider: custom but no factory supplied" };
2965
+ case "mock":
2966
+ return { provider: new MockAgentService(), reason: "provider: mock" };
2967
+ case "none":
2968
+ return { provider: new NullAgentService(), reason: "provider: none (the default)" };
2969
+ default:
2970
+ return {
2971
+ provider: new NullAgentService(),
2972
+ reason: `unrecognised provider ${String(config.provider)} \u2014 falling back to none`
2973
+ };
2974
+ }
2975
+ }
2976
+ function createAgentService(config = { provider: "none" }) {
2977
+ return decide(config).provider;
2978
+ }
2979
+ export {
2980
+ ANNOUNCE_FULL_TEXT_MAX_CHARS,
2981
+ ActionCard,
2982
+ ActionResult,
2983
+ AgentAppShell,
2984
+ AgentAvatar,
2985
+ AgentKitHostProvider,
2986
+ AgentOpsPanel,
2987
+ AgentPicker,
2988
+ AgentStatusBadge,
2989
+ AnswerWithCitations,
2990
+ AppRail,
2991
+ ApprovalCard,
2992
+ AvailabilityBanner,
2993
+ CitationChip,
2994
+ CitationList,
2995
+ Composer,
2996
+ ConfirmInline,
2997
+ ConversationHeader,
2998
+ ConversationList,
2999
+ ConversationListItem,
3000
+ DEFAULT_GROUP_WINDOW_MS,
3001
+ DayDivider,
3002
+ EmptyConversation,
3003
+ GlobalSearchBar,
3004
+ GroundingPanel,
3005
+ LiveAnnouncer,
3006
+ MessageActions,
3007
+ MessageBubble,
3008
+ MessageList,
3009
+ MockAgentService,
3010
+ ModelBadge,
3011
+ NO_ANNOUNCEMENT,
3012
+ NullAgentService,
3013
+ ParameterReview,
3014
+ ProviderAttribution,
3015
+ RightPane,
3016
+ RunHistoryPanel,
3017
+ RunTimeline,
3018
+ STICK_TO_BOTTOM_THRESHOLD_PX,
3019
+ SourceCard,
3020
+ StreamingText,
3021
+ SuggestedPrompts,
3022
+ SystemNotice,
3023
+ ThrottleNotice,
3024
+ ToolCallCard,
3025
+ TypingIndicator,
3026
+ UsageMeter,
3027
+ agentDarkTheme,
3028
+ agentTheme,
3029
+ appendUserMessage,
3030
+ applyAgentEvent,
3031
+ bucketByDay,
3032
+ classifyTransportFailure,
3033
+ conversationAccessibleName,
3034
+ createAgentDarkTheme,
3035
+ createAgentService,
3036
+ createAgentTheme,
3037
+ createIdPrefixCounter,
3038
+ dayKeyOf,
3039
+ decide,
3040
+ deriveAnnouncement,
3041
+ extractCitationMarkers,
3042
+ formatDayLabel,
3043
+ fromClaudeStreamEvent,
3044
+ fromFoundryChatResult,
3045
+ fromOpenAiChatChunk,
3046
+ fromSseLine,
3047
+ groupMessages,
3048
+ immediateScheduler,
3049
+ initialConversation,
3050
+ isSameAuthorRun,
3051
+ isTerminalStatus,
3052
+ messageAttribution,
3053
+ mockScenarios,
3054
+ nextAgentIdPrefix,
3055
+ realScheduler,
3056
+ renumberCitations,
3057
+ resolveHostTheme,
3058
+ runStatusLabel,
3059
+ runStatusSeverity,
3060
+ shouldStickToBottom,
3061
+ splitTextByCitations,
3062
+ useConversation,
3063
+ useFocusRestore,
3064
+ useRovingTabindex,
3065
+ useStickyScroll,
3066
+ useVisuallyHiddenStyles
3067
+ };
3068
+ //# sourceMappingURL=index.mjs.map