@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/README.md +166 -0
- package/dist/core.cjs +532 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +278 -0
- package/dist/core.d.ts +278 -0
- package/dist/core.mjs +482 -0
- package/dist/core.mjs.map +1 -0
- package/dist/host.cjs +113 -0
- package/dist/host.cjs.map +1 -0
- package/dist/host.d.cts +3 -0
- package/dist/host.d.ts +3 -0
- package/dist/host.mjs +74 -0
- package/dist/host.mjs.map +1 -0
- package/dist/index-DGN6eYnp.d.cts +103 -0
- package/dist/index-DGN6eYnp.d.ts +103 -0
- package/dist/index.cjs +3145 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +863 -0
- package/dist/index.d.ts +863 -0
- package/dist/index.mjs +3068 -0
- package/dist/index.mjs.map +1 -0
- package/dist/protocol-CZJ1v9Hq.d.cts +270 -0
- package/dist/protocol-CZJ1v9Hq.d.ts +270 -0
- package/dist/services.cjs +333 -0
- package/dist/services.cjs.map +1 -0
- package/dist/services.d.cts +118 -0
- package/dist/services.d.ts +118 -0
- package/dist/services.mjs +300 -0
- package/dist/services.mjs.map +1 -0
- package/package.json +89 -0
package/dist/core.mjs
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
// src/core/runStatus.ts
|
|
2
|
+
var TERMINAL = /* @__PURE__ */ new Set([
|
|
3
|
+
"succeeded",
|
|
4
|
+
"blocked",
|
|
5
|
+
"refused",
|
|
6
|
+
"error",
|
|
7
|
+
"notProvisioned",
|
|
8
|
+
"cancelled"
|
|
9
|
+
]);
|
|
10
|
+
function isTerminalStatus(status) {
|
|
11
|
+
return TERMINAL.has(status);
|
|
12
|
+
}
|
|
13
|
+
function runStatusSeverity(status) {
|
|
14
|
+
switch (status) {
|
|
15
|
+
case "succeeded":
|
|
16
|
+
return "success";
|
|
17
|
+
case "blocked":
|
|
18
|
+
case "refused":
|
|
19
|
+
return "warning";
|
|
20
|
+
case "error":
|
|
21
|
+
return "error";
|
|
22
|
+
case "notProvisioned":
|
|
23
|
+
case "cancelled":
|
|
24
|
+
return "info";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function classifyTransportFailure(input) {
|
|
28
|
+
const { httpStatus, code, retryAfterMs } = input;
|
|
29
|
+
const message = input.message ?? "The agent could not be reached.";
|
|
30
|
+
if (httpStatus === 503) {
|
|
31
|
+
return {
|
|
32
|
+
status: "notProvisioned",
|
|
33
|
+
message,
|
|
34
|
+
code: code ?? "not_provisioned",
|
|
35
|
+
recoverable: false,
|
|
36
|
+
correlationId: void 0
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (httpStatus === 429) {
|
|
40
|
+
return { status: "error", message, code: code ?? "rate_limited", recoverable: true, retryAfterMs };
|
|
41
|
+
}
|
|
42
|
+
if (code === "content_filter") {
|
|
43
|
+
return { status: "blocked", message, code, recoverable: false };
|
|
44
|
+
}
|
|
45
|
+
return { status: "error", message, code, recoverable: httpStatus !== void 0 && httpStatus >= 500 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/core/grouping.ts
|
|
49
|
+
var DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1e3;
|
|
50
|
+
function dayKey(epochMs, timeZone) {
|
|
51
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
52
|
+
timeZone,
|
|
53
|
+
year: "numeric",
|
|
54
|
+
month: "2-digit",
|
|
55
|
+
day: "2-digit"
|
|
56
|
+
}).format(new Date(epochMs));
|
|
57
|
+
}
|
|
58
|
+
function isSameAuthorRun(a, b, windowMs = DEFAULT_GROUP_WINDOW_MS, timeZone) {
|
|
59
|
+
if (a.authorId !== b.authorId) return false;
|
|
60
|
+
if (a.role !== b.role) return false;
|
|
61
|
+
if (a.role === "system" || b.role === "system") return false;
|
|
62
|
+
if (b.createdAt < a.createdAt) return false;
|
|
63
|
+
if (b.createdAt - a.createdAt > windowMs) return false;
|
|
64
|
+
return dayKey(a.createdAt, timeZone) === dayKey(b.createdAt, timeZone);
|
|
65
|
+
}
|
|
66
|
+
function groupMessages(messages, options = {}) {
|
|
67
|
+
const windowMs = options.windowMs ?? DEFAULT_GROUP_WINDOW_MS;
|
|
68
|
+
const groups = [];
|
|
69
|
+
for (const message of messages) {
|
|
70
|
+
const current = groups[groups.length - 1];
|
|
71
|
+
const previousId = current?.messageIds[current.messageIds.length - 1];
|
|
72
|
+
const previous = previousId ? messages.find((m) => m.id === previousId) : void 0;
|
|
73
|
+
if (current && previous && isSameAuthorRun(previous, message, windowMs, options.timeZone)) {
|
|
74
|
+
current.messageIds.push(message.id);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
groups.push({
|
|
78
|
+
key: `${message.authorId}:${message.id}`,
|
|
79
|
+
authorId: message.authorId,
|
|
80
|
+
role: message.role,
|
|
81
|
+
startedAt: message.createdAt,
|
|
82
|
+
messageIds: [message.id]
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return groups;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/core/dayDividers.ts
|
|
89
|
+
function dayKeyOf(epochMs, timeZone) {
|
|
90
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
91
|
+
timeZone,
|
|
92
|
+
year: "numeric",
|
|
93
|
+
month: "2-digit",
|
|
94
|
+
day: "2-digit"
|
|
95
|
+
}).format(new Date(epochMs));
|
|
96
|
+
}
|
|
97
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
98
|
+
function labelFor(dayKey2, nowMs, timeZone) {
|
|
99
|
+
const todayKey = dayKeyOf(nowMs, timeZone);
|
|
100
|
+
if (dayKey2 === todayKey) return { kind: "today" };
|
|
101
|
+
if (dayKey2 === dayKeyOf(nowMs - DAY_MS, timeZone)) return { kind: "yesterday" };
|
|
102
|
+
const asDate = /* @__PURE__ */ new Date(`${dayKey2}T00:00:00Z`);
|
|
103
|
+
for (let back = 2; back <= 6; back++) {
|
|
104
|
+
if (dayKey2 === dayKeyOf(nowMs - back * DAY_MS, timeZone)) {
|
|
105
|
+
return { kind: "weekday", weekday: asDate.getUTCDay(), iso: dayKey2 };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { kind: "date", iso: dayKey2 };
|
|
109
|
+
}
|
|
110
|
+
function bucketByDay(messages, options = {}) {
|
|
111
|
+
const nowMs = options.now ?? Date.now();
|
|
112
|
+
const buckets = [];
|
|
113
|
+
for (const message of messages) {
|
|
114
|
+
const key = dayKeyOf(message.createdAt, options.timeZone);
|
|
115
|
+
const current = buckets[buckets.length - 1];
|
|
116
|
+
if (current && current.dayKey === key) {
|
|
117
|
+
current.messageIds.push(message.id);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
buckets.push({
|
|
121
|
+
dayKey: key,
|
|
122
|
+
label: labelFor(key, nowMs, options.timeZone),
|
|
123
|
+
messageIds: [message.id]
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return buckets;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/core/citations.ts
|
|
130
|
+
var BRACKET_MARKER = /\[(\d{1,3})\]/g;
|
|
131
|
+
var AOAI_MARKER = /【(\d{1,3}):\d+†[^】]*】/g;
|
|
132
|
+
function extractCitationMarkers(text) {
|
|
133
|
+
const found = [];
|
|
134
|
+
for (const re of [BRACKET_MARKER, AOAI_MARKER]) {
|
|
135
|
+
re.lastIndex = 0;
|
|
136
|
+
let m;
|
|
137
|
+
while ((m = re.exec(text)) !== null) {
|
|
138
|
+
found.push({ marker: m[1], index: m.index, length: m[0].length });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return found.sort((a, b) => a.index - b.index);
|
|
142
|
+
}
|
|
143
|
+
function renumberCitations(text, sources) {
|
|
144
|
+
const markers = extractCitationMarkers(text);
|
|
145
|
+
const order = [];
|
|
146
|
+
for (const { marker } of markers) if (!order.includes(marker)) order.push(marker);
|
|
147
|
+
const byOriginal = /* @__PURE__ */ new Map();
|
|
148
|
+
order.forEach((marker, i) => byOriginal.set(marker, i + 1));
|
|
149
|
+
const used = [];
|
|
150
|
+
const citations = [];
|
|
151
|
+
for (const marker of order) {
|
|
152
|
+
const source = sources[Number(marker) - 1];
|
|
153
|
+
if (!source) continue;
|
|
154
|
+
used.push(source);
|
|
155
|
+
citations.push({ marker: String(byOriginal.get(marker)), sourceId: source.id });
|
|
156
|
+
}
|
|
157
|
+
let out = "";
|
|
158
|
+
let cursor = 0;
|
|
159
|
+
for (const { marker, index, length } of markers) {
|
|
160
|
+
out += text.slice(cursor, index);
|
|
161
|
+
const renumbered = byOriginal.get(marker);
|
|
162
|
+
out += renumbered === void 0 ? "" : `[${renumbered}]`;
|
|
163
|
+
cursor = index + length;
|
|
164
|
+
}
|
|
165
|
+
out += text.slice(cursor);
|
|
166
|
+
return { text: out, used, citations };
|
|
167
|
+
}
|
|
168
|
+
function splitTextByCitations(text, citations) {
|
|
169
|
+
const markers = extractCitationMarkers(text);
|
|
170
|
+
if (markers.length === 0) return text ? [{ kind: "text", text }] : [];
|
|
171
|
+
const segments = [];
|
|
172
|
+
let cursor = 0;
|
|
173
|
+
for (const { marker, index, length } of markers) {
|
|
174
|
+
if (index > cursor) segments.push({ kind: "text", text: text.slice(cursor, index) });
|
|
175
|
+
const citation = citations.find((c) => c.marker === marker);
|
|
176
|
+
segments.push({
|
|
177
|
+
kind: "citation",
|
|
178
|
+
text: `[${marker}]`,
|
|
179
|
+
marker,
|
|
180
|
+
sourceId: citation?.sourceId
|
|
181
|
+
});
|
|
182
|
+
cursor = index + length;
|
|
183
|
+
}
|
|
184
|
+
if (cursor < text.length) segments.push({ kind: "text", text: text.slice(cursor) });
|
|
185
|
+
return segments;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/core/conversationReducer.ts
|
|
189
|
+
function initialConversation(seed = []) {
|
|
190
|
+
return { messages: [...seed], streamingMessageId: null, status: "idle" };
|
|
191
|
+
}
|
|
192
|
+
function patch(state, messageId, update) {
|
|
193
|
+
const i = state.messages.findIndex((m) => m.id === messageId);
|
|
194
|
+
if (i === -1) return state.messages;
|
|
195
|
+
const next = state.messages.slice();
|
|
196
|
+
next[i] = update(next[i]);
|
|
197
|
+
return next;
|
|
198
|
+
}
|
|
199
|
+
function appendUserMessage(state, input) {
|
|
200
|
+
const message = {
|
|
201
|
+
id: input.id,
|
|
202
|
+
role: "user",
|
|
203
|
+
authorId: input.authorId ?? "user",
|
|
204
|
+
text: input.text,
|
|
205
|
+
createdAt: input.now ?? Date.now(),
|
|
206
|
+
attachments: input.attachments
|
|
207
|
+
};
|
|
208
|
+
return { ...state, messages: [...state.messages, message], status: "connecting", failure: void 0 };
|
|
209
|
+
}
|
|
210
|
+
function applyAgentEvent(state, event) {
|
|
211
|
+
switch (event.type) {
|
|
212
|
+
case "turn_start": {
|
|
213
|
+
const message = {
|
|
214
|
+
id: event.messageId,
|
|
215
|
+
role: "assistant",
|
|
216
|
+
authorId: event.provider ?? "agent",
|
|
217
|
+
text: "",
|
|
218
|
+
createdAt: Date.now(),
|
|
219
|
+
streaming: true,
|
|
220
|
+
provider: event.provider
|
|
221
|
+
};
|
|
222
|
+
return {
|
|
223
|
+
...state,
|
|
224
|
+
messages: [...state.messages, message],
|
|
225
|
+
streamingMessageId: event.messageId,
|
|
226
|
+
status: "streaming",
|
|
227
|
+
statusLabel: void 0,
|
|
228
|
+
failure: void 0,
|
|
229
|
+
suggestions: void 0
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
case "text_delta":
|
|
233
|
+
return {
|
|
234
|
+
...state,
|
|
235
|
+
messages: patch(state, event.messageId, (m) => ({ ...m, text: m.text + event.text }))
|
|
236
|
+
};
|
|
237
|
+
case "thinking_delta":
|
|
238
|
+
return {
|
|
239
|
+
...state,
|
|
240
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
241
|
+
...m,
|
|
242
|
+
thinking: (m.thinking ?? "") + event.text
|
|
243
|
+
}))
|
|
244
|
+
};
|
|
245
|
+
case "tool_call":
|
|
246
|
+
return {
|
|
247
|
+
...state,
|
|
248
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
249
|
+
...m,
|
|
250
|
+
toolCalls: [...m.toolCalls ?? [], event.call]
|
|
251
|
+
}))
|
|
252
|
+
};
|
|
253
|
+
case "tool_result":
|
|
254
|
+
return {
|
|
255
|
+
...state,
|
|
256
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
257
|
+
...m,
|
|
258
|
+
toolCalls: (m.toolCalls ?? []).map(
|
|
259
|
+
(c) => c.id === event.callId ? { ...c, status: event.status, resultText: event.resultText, failure: event.failure } : c
|
|
260
|
+
)
|
|
261
|
+
}))
|
|
262
|
+
};
|
|
263
|
+
case "sources":
|
|
264
|
+
return {
|
|
265
|
+
...state,
|
|
266
|
+
messages: patch(state, event.messageId, (m) => ({ ...m, sources: event.sources }))
|
|
267
|
+
};
|
|
268
|
+
case "citation":
|
|
269
|
+
return {
|
|
270
|
+
...state,
|
|
271
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
272
|
+
...m,
|
|
273
|
+
citations: [...m.citations ?? [], event.citation]
|
|
274
|
+
}))
|
|
275
|
+
};
|
|
276
|
+
case "suggestions":
|
|
277
|
+
return { ...state, suggestions: event.prompts };
|
|
278
|
+
case "status":
|
|
279
|
+
return { ...state, statusLabel: event.label };
|
|
280
|
+
case "turn_end": {
|
|
281
|
+
const { result } = event;
|
|
282
|
+
return {
|
|
283
|
+
...state,
|
|
284
|
+
messages: patch(state, event.messageId, (m) => ({
|
|
285
|
+
...m,
|
|
286
|
+
// A non-streaming provider sends no deltas, so the assembled text arrives here.
|
|
287
|
+
text: m.text.length > 0 ? m.text : result.text,
|
|
288
|
+
streaming: false,
|
|
289
|
+
guarded: result.guarded,
|
|
290
|
+
provider: result.provider,
|
|
291
|
+
usage: result.usage,
|
|
292
|
+
failure: result.failure
|
|
293
|
+
})),
|
|
294
|
+
streamingMessageId: null,
|
|
295
|
+
status: result.status,
|
|
296
|
+
statusLabel: void 0,
|
|
297
|
+
failure: result.failure,
|
|
298
|
+
usage: result.usage
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/core/normalize.ts
|
|
305
|
+
function fromSseLine(line) {
|
|
306
|
+
const trimmed = line.trim();
|
|
307
|
+
if (trimmed === "" || trimmed.startsWith(":")) return void 0;
|
|
308
|
+
if (!trimmed.startsWith("data:")) return void 0;
|
|
309
|
+
const payload = trimmed.slice("data:".length).trim();
|
|
310
|
+
if (payload === "") return void 0;
|
|
311
|
+
if (payload === "[DONE]") return "[DONE]";
|
|
312
|
+
try {
|
|
313
|
+
return JSON.parse(payload);
|
|
314
|
+
} catch {
|
|
315
|
+
return void 0;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function fromOpenAiChatChunk(chunk, ctx) {
|
|
319
|
+
const events = [];
|
|
320
|
+
const delta = chunk?.choices?.[0]?.delta;
|
|
321
|
+
if (!delta) return events;
|
|
322
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
323
|
+
events.push({ type: "text_delta", messageId: ctx.messageId, text: delta.content });
|
|
324
|
+
}
|
|
325
|
+
for (const call of delta.tool_calls ?? []) {
|
|
326
|
+
if (!call.id) continue;
|
|
327
|
+
const toolCall = {
|
|
328
|
+
id: call.id,
|
|
329
|
+
name: call.function?.name ?? "",
|
|
330
|
+
argumentsJson: call.function?.arguments ?? "",
|
|
331
|
+
status: "proposed"
|
|
332
|
+
};
|
|
333
|
+
events.push({ type: "tool_call", messageId: ctx.messageId, call: toolCall });
|
|
334
|
+
}
|
|
335
|
+
return events;
|
|
336
|
+
}
|
|
337
|
+
function fromClaudeStreamEvent(event, ctx) {
|
|
338
|
+
switch (event.type) {
|
|
339
|
+
case "init":
|
|
340
|
+
return [
|
|
341
|
+
{
|
|
342
|
+
type: "turn_start",
|
|
343
|
+
turnId: ctx.turnId,
|
|
344
|
+
messageId: ctx.messageId,
|
|
345
|
+
model: event.model,
|
|
346
|
+
provider: "claude-bridge",
|
|
347
|
+
sessionId: event.sessionId
|
|
348
|
+
}
|
|
349
|
+
];
|
|
350
|
+
case "text_delta":
|
|
351
|
+
return event.text ? [{ type: "text_delta", messageId: ctx.messageId, text: event.text }] : [];
|
|
352
|
+
case "thinking_delta":
|
|
353
|
+
return event.text ? [{ type: "thinking_delta", messageId: ctx.messageId, text: event.text }] : [];
|
|
354
|
+
case "done":
|
|
355
|
+
return [
|
|
356
|
+
{
|
|
357
|
+
type: "turn_end",
|
|
358
|
+
turnId: ctx.turnId,
|
|
359
|
+
messageId: ctx.messageId,
|
|
360
|
+
result: {
|
|
361
|
+
status: "succeeded",
|
|
362
|
+
text: event.fullText ?? "",
|
|
363
|
+
provider: "claude-bridge",
|
|
364
|
+
model: event.model,
|
|
365
|
+
usage: {
|
|
366
|
+
inputTokens: event.inputTokens,
|
|
367
|
+
outputTokens: event.outputTokens,
|
|
368
|
+
cachedTokens: event.cacheReadTokens,
|
|
369
|
+
costUsd: event.costUsd,
|
|
370
|
+
durationMs: event.durationMs,
|
|
371
|
+
model: event.model
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
];
|
|
376
|
+
case "error":
|
|
377
|
+
return [
|
|
378
|
+
{
|
|
379
|
+
type: "turn_end",
|
|
380
|
+
turnId: ctx.turnId,
|
|
381
|
+
messageId: ctx.messageId,
|
|
382
|
+
result: {
|
|
383
|
+
status: "error",
|
|
384
|
+
text: "",
|
|
385
|
+
provider: "claude-bridge",
|
|
386
|
+
failure: {
|
|
387
|
+
status: "error",
|
|
388
|
+
message: event.message ?? "The agent failed.",
|
|
389
|
+
recoverable: event.recoverable ?? false
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function fromFoundryChatResult(result, ctx, provider = "foundry") {
|
|
397
|
+
return [
|
|
398
|
+
{ type: "turn_start", turnId: ctx.turnId, messageId: ctx.messageId, model: result.model, provider },
|
|
399
|
+
{
|
|
400
|
+
type: "turn_end",
|
|
401
|
+
turnId: ctx.turnId,
|
|
402
|
+
messageId: ctx.messageId,
|
|
403
|
+
result: {
|
|
404
|
+
status: "succeeded",
|
|
405
|
+
text: result.text,
|
|
406
|
+
provider,
|
|
407
|
+
model: result.model,
|
|
408
|
+
usage: {
|
|
409
|
+
inputTokens: result.promptTokens,
|
|
410
|
+
outputTokens: result.completionTokens,
|
|
411
|
+
model: result.model
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
];
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/core/announce.ts
|
|
419
|
+
var ANNOUNCE_FULL_TEXT_MAX_CHARS = 240;
|
|
420
|
+
var NO_ANNOUNCEMENT = { polite: "", assertive: "" };
|
|
421
|
+
var TERMINAL2 = /* @__PURE__ */ new Set(["succeeded", "blocked", "refused", "error", "notProvisioned", "cancelled"]);
|
|
422
|
+
function deriveAnnouncement(state, previous) {
|
|
423
|
+
const wasTerminal = previous ? TERMINAL2.has(previous.status) : false;
|
|
424
|
+
const isTerminal = TERMINAL2.has(state.status);
|
|
425
|
+
if (!isTerminal || wasTerminal) return NO_ANNOUNCEMENT;
|
|
426
|
+
if (state.failure) {
|
|
427
|
+
return { polite: "", assertive: state.failure.message };
|
|
428
|
+
}
|
|
429
|
+
const last = state.messages[state.messages.length - 1];
|
|
430
|
+
const text = last?.text ?? "";
|
|
431
|
+
if (!text) return NO_ANNOUNCEMENT;
|
|
432
|
+
const guarded = last?.guarded ? "The response was withheld. " : "";
|
|
433
|
+
if (text.length <= ANNOUNCE_FULL_TEXT_MAX_CHARS) {
|
|
434
|
+
return { polite: `${guarded}${text}`, assertive: "" };
|
|
435
|
+
}
|
|
436
|
+
const words = text.trim().split(/\s+/).length;
|
|
437
|
+
return {
|
|
438
|
+
polite: `${guarded}Response complete, ${words} words.`,
|
|
439
|
+
assertive: ""
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/core/scroll.ts
|
|
444
|
+
var STICK_TO_BOTTOM_THRESHOLD_PX = 32;
|
|
445
|
+
function shouldStickToBottom(geometry, thresholdPx = STICK_TO_BOTTOM_THRESHOLD_PX) {
|
|
446
|
+
const { scrollTop, scrollHeight, clientHeight } = geometry;
|
|
447
|
+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
|
448
|
+
return distanceFromBottom <= thresholdPx;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/core/ids.ts
|
|
452
|
+
function createIdPrefixCounter(tag) {
|
|
453
|
+
let n = 0;
|
|
454
|
+
return () => `${tag}${++n}-`;
|
|
455
|
+
}
|
|
456
|
+
export {
|
|
457
|
+
ANNOUNCE_FULL_TEXT_MAX_CHARS,
|
|
458
|
+
DEFAULT_GROUP_WINDOW_MS,
|
|
459
|
+
NO_ANNOUNCEMENT,
|
|
460
|
+
STICK_TO_BOTTOM_THRESHOLD_PX,
|
|
461
|
+
appendUserMessage,
|
|
462
|
+
applyAgentEvent,
|
|
463
|
+
bucketByDay,
|
|
464
|
+
classifyTransportFailure,
|
|
465
|
+
createIdPrefixCounter,
|
|
466
|
+
dayKeyOf,
|
|
467
|
+
deriveAnnouncement,
|
|
468
|
+
extractCitationMarkers,
|
|
469
|
+
fromClaudeStreamEvent,
|
|
470
|
+
fromFoundryChatResult,
|
|
471
|
+
fromOpenAiChatChunk,
|
|
472
|
+
fromSseLine,
|
|
473
|
+
groupMessages,
|
|
474
|
+
initialConversation,
|
|
475
|
+
isSameAuthorRun,
|
|
476
|
+
isTerminalStatus,
|
|
477
|
+
renumberCitations,
|
|
478
|
+
runStatusSeverity,
|
|
479
|
+
shouldStickToBottom,
|
|
480
|
+
splitTextByCitations
|
|
481
|
+
};
|
|
482
|
+
//# sourceMappingURL=core.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/runStatus.ts","../src/core/grouping.ts","../src/core/dayDividers.ts","../src/core/citations.ts","../src/core/conversationReducer.ts","../src/core/normalize.ts","../src/core/announce.ts","../src/core/scroll.ts","../src/core/ids.ts"],"sourcesContent":["import type { AgentFailure, AgentRunStatus } from './protocol';\n\n/** Terminal statuses. `succeeded` included — this answers \"is the turn over?\", not \"did it work?\". */\nconst TERMINAL: ReadonlySet<AgentRunStatus> = new Set<AgentRunStatus>([\n 'succeeded',\n 'blocked',\n 'refused',\n 'error',\n 'notProvisioned',\n 'cancelled',\n]);\n\nexport function isTerminalStatus(status: AgentRunStatus): boolean {\n return TERMINAL.has(status);\n}\n\n/** How a status should READ, not what colour it is — the UI maps this to tokens. */\nexport function runStatusSeverity(\n status: AgentRunStatus,\n): 'success' | 'warning' | 'error' | 'info' {\n switch (status) {\n case 'succeeded':\n return 'success';\n case 'blocked':\n case 'refused':\n return 'warning';\n case 'error':\n return 'error';\n case 'notProvisioned':\n case 'cancelled':\n return 'info';\n }\n}\n\n/**\n * Classify a transport failure into the six-state ladder.\n *\n * ★★ 503 maps to `notProvisioned`, NOT `error`. Adopted from the ai-103 broker, which\n * records the reason: with on-demand Azure most services are absent most of the time,\n * and that is a normal state — reporting it as a failure is what would make the app look\n * broken. Without this branch, \"nobody has turned this on yet\" and \"it is broken\" are\n * indistinguishable on screen, and they need completely different fixes.\n */\nexport function classifyTransportFailure(input: {\n httpStatus?: number;\n code?: string;\n message?: string;\n retryAfterMs?: number;\n}): AgentFailure {\n const { httpStatus, code, retryAfterMs } = input;\n const message = input.message ?? 'The agent could not be reached.';\n\n if (httpStatus === 503) {\n return {\n status: 'notProvisioned',\n message,\n code: code ?? 'not_provisioned',\n recoverable: false,\n correlationId: undefined,\n };\n }\n if (httpStatus === 429) {\n return { status: 'error', message, code: code ?? 'rate_limited', recoverable: true, retryAfterMs };\n }\n if (code === 'content_filter') {\n return { status: 'blocked', message, code, recoverable: false };\n }\n return { status: 'error', message, code, recoverable: httpStatus !== undefined && httpStatus >= 500 };\n}\n","import type { AgentMessage, AgentRole } from './protocol';\n\n/** Teams groups consecutive messages from one author inside a short window. */\nexport const DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1000;\n\nexport interface GroupingOptions {\n windowMs?: number;\n /** Injected so day-boundary behaviour is testable without touching the system clock. */\n timeZone?: string;\n}\n\nexport interface MessageGroup {\n key: string;\n authorId: string;\n role: AgentRole;\n startedAt: number;\n messageIds: string[];\n}\n\n/** Local calendar day, in the given zone. Duplicated deliberately from dayDividers to keep\n * this module independent of it; both delegate to Intl, so they cannot disagree. */\nfunction dayKey(epochMs: number, timeZone?: string): string {\n return new Intl.DateTimeFormat('en-CA', {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).format(new Date(epochMs));\n}\n\n/**\n * Do these two messages belong to the same visual run?\n *\n * ★ Exported separately from `groupMessages` on purpose. The boundary cases (4:59 / exactly\n * 5:00 / 5:01) are then assertable against the PREDICATE, instead of being inferred from\n * a group count — where an off-by-one and a correct implementation can produce the same\n * number of groups for the wrong reason.\n */\nexport function isSameAuthorRun(\n a: AgentMessage,\n b: AgentMessage,\n windowMs: number = DEFAULT_GROUP_WINDOW_MS,\n timeZone?: string,\n): boolean {\n if (a.authorId !== b.authorId) return false;\n if (a.role !== b.role) return false;\n // A system notice between two messages ends the run: the second is a new utterance.\n if (a.role === 'system' || b.role === 'system') return false;\n if (b.createdAt < a.createdAt) return false;\n if (b.createdAt - a.createdAt > windowMs) return false;\n // Never group across a day divider — the divider would have to render inside the group.\n return dayKey(a.createdAt, timeZone) === dayKey(b.createdAt, timeZone);\n}\n\n/**\n * Collapse consecutive same-author messages into runs, Teams-style: the avatar and name\n * render once per run.\n *\n * ★ Grouping is a VISUAL affordance only. Every message must still carry its author in the\n * accessible name — a screen reader navigating message-by-message would otherwise hear\n * nothing identifying messages 2..N of a run, in a UI whose entire point is the\n * distinction between what the user said and what the model said.\n */\nexport function groupMessages(\n messages: readonly AgentMessage[],\n options: GroupingOptions = {},\n): MessageGroup[] {\n const windowMs = options.windowMs ?? DEFAULT_GROUP_WINDOW_MS;\n const groups: MessageGroup[] = [];\n\n for (const message of messages) {\n const current = groups[groups.length - 1];\n const previousId = current?.messageIds[current.messageIds.length - 1];\n const previous = previousId ? messages.find((m) => m.id === previousId) : undefined;\n\n if (current && previous && isSameAuthorRun(previous, message, windowMs, options.timeZone)) {\n current.messageIds.push(message.id);\n continue;\n }\n groups.push({\n key: `${message.authorId}:${message.id}`,\n authorId: message.authorId,\n role: message.role,\n startedAt: message.createdAt,\n messageIds: [message.id],\n });\n }\n return groups;\n}\n","import type { AgentMessage } from './protocol';\n\n/**\n * A day divider as a DESCRIPTOR, never a localised string.\n *\n * ★ A pure-TS module that returned \"Yesterday\" could not be localised by the host and could\n * not be tested deterministically. The React layer maps this through `Intl.DateTimeFormat`.\n */\nexport type DayDividerLabel =\n | { kind: 'today' }\n | { kind: 'yesterday' }\n | { kind: 'weekday'; weekday: number; iso: string }\n | { kind: 'date'; iso: string };\n\nexport interface DayBucket {\n /** `YYYY-MM-DD` in the resolved zone. */\n dayKey: string;\n label: DayDividerLabel;\n messageIds: string[];\n}\n\n/** Local calendar day in `timeZone`. `en-CA` because it formats as `YYYY-MM-DD`. */\nexport function dayKeyOf(epochMs: number, timeZone?: string): string {\n return new Intl.DateTimeFormat('en-CA', {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).format(new Date(epochMs));\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nfunction labelFor(dayKey: string, nowMs: number, timeZone?: string): DayDividerLabel {\n const todayKey = dayKeyOf(nowMs, timeZone);\n if (dayKey === todayKey) return { kind: 'today' };\n if (dayKey === dayKeyOf(nowMs - DAY_MS, timeZone)) return { kind: 'yesterday' };\n\n // Within the last week, Teams shows the weekday name.\n const asDate = new Date(`${dayKey}T00:00:00Z`);\n for (let back = 2; back <= 6; back++) {\n if (dayKey === dayKeyOf(nowMs - back * DAY_MS, timeZone)) {\n return { kind: 'weekday', weekday: asDate.getUTCDay(), iso: dayKey };\n }\n }\n return { kind: 'date', iso: dayKey };\n}\n\n/**\n * Bucket messages by local calendar day.\n *\n * ★ `now` and `timeZone` are INJECTED, not read from the environment. That is what makes\n * the claim expressible at all: the test that separates a correct implementation from a\n * naive `new Date().toDateString()` is the one that feeds the SAME epoch with two zones\n * either side of the date line and asserts the keys DIFFER. A UTC-only test passes for\n * both.\n */\nexport function bucketByDay(\n messages: readonly AgentMessage[],\n options: { timeZone?: string; now?: number } = {},\n): DayBucket[] {\n const nowMs = options.now ?? Date.now();\n const buckets: DayBucket[] = [];\n\n for (const message of messages) {\n const key = dayKeyOf(message.createdAt, options.timeZone);\n const current = buckets[buckets.length - 1];\n if (current && current.dayKey === key) {\n current.messageIds.push(message.id);\n continue;\n }\n buckets.push({\n dayKey: key,\n label: labelFor(key, nowMs, options.timeZone),\n messageIds: [message.id],\n });\n }\n return buckets;\n}\n","import type { AgentCitation, AgentSource } from './protocol';\n\nexport interface CitationSegment {\n kind: 'text' | 'citation';\n text: string;\n marker?: string;\n sourceId?: string;\n}\n\n/** `[1]` and the Azure OpenAI annotation form `【3:0†source】`. */\nconst BRACKET_MARKER = /\\[(\\d{1,3})\\]/g;\nconst AOAI_MARKER = /【(\\d{1,3}):\\d+†[^】]*】/g;\n\nexport interface ExtractedMarker {\n marker: string;\n index: number;\n length: number;\n}\n\n/** Every citation marker in the text, in order of appearance. */\nexport function extractCitationMarkers(text: string): ExtractedMarker[] {\n const found: ExtractedMarker[] = [];\n for (const re of [BRACKET_MARKER, AOAI_MARKER]) {\n re.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n found.push({ marker: m[1], index: m.index, length: m[0].length });\n }\n }\n return found.sort((a, b) => a.index - b.index);\n}\n\n/**\n * Renumber an answer's citations to `[1]`, `[2]`… in first-appearance order, and return\n * ONLY the sources the text actually cites.\n *\n * ★★ Dropping uncited sources is the point of this function, not a side effect. A grounding\n * panel listing twelve retrieved chunks under a two-citation answer is the defect it\n * exists to prevent: it implies the answer rests on twelve things when it rests on two,\n * which is exactly the kind of overstated provenance that makes a citation panel\n * decorative rather than trustworthy.\n */\nexport function renumberCitations(\n text: string,\n sources: readonly AgentSource[],\n): { text: string; used: AgentSource[]; citations: AgentCitation[] } {\n const markers = extractCitationMarkers(text);\n const order: string[] = [];\n for (const { marker } of markers) if (!order.includes(marker)) order.push(marker);\n\n const byOriginal = new Map<string, number>();\n order.forEach((marker, i) => byOriginal.set(marker, i + 1));\n\n const used: AgentSource[] = [];\n const citations: AgentCitation[] = [];\n for (const marker of order) {\n // Markers are 1-based positions into the ORIGINAL source list.\n const source = sources[Number(marker) - 1];\n if (!source) continue;\n used.push(source);\n citations.push({ marker: String(byOriginal.get(marker)), sourceId: source.id });\n }\n\n let out = '';\n let cursor = 0;\n for (const { marker, index, length } of markers) {\n out += text.slice(cursor, index);\n const renumbered = byOriginal.get(marker);\n out += renumbered === undefined ? '' : `[${renumbered}]`;\n cursor = index + length;\n }\n out += text.slice(cursor);\n\n return { text: out, used, citations };\n}\n\n/** Split text into runs so the renderer can put a chip where each marker was. */\nexport function splitTextByCitations(\n text: string,\n citations: readonly AgentCitation[],\n): CitationSegment[] {\n const markers = extractCitationMarkers(text);\n if (markers.length === 0) return text ? [{ kind: 'text', text }] : [];\n\n const segments: CitationSegment[] = [];\n let cursor = 0;\n for (const { marker, index, length } of markers) {\n if (index > cursor) segments.push({ kind: 'text', text: text.slice(cursor, index) });\n const citation = citations.find((c) => c.marker === marker);\n segments.push({\n kind: 'citation',\n text: `[${marker}]`,\n marker,\n sourceId: citation?.sourceId,\n });\n cursor = index + length;\n }\n if (cursor < text.length) segments.push({ kind: 'text', text: text.slice(cursor) });\n return segments;\n}\n","import type {\n AgentAttachment,\n AgentEvent,\n AgentFailure,\n AgentMessage,\n AgentRunStatus,\n AgentUsage,\n} from './protocol';\n\n/** One field, not a `{loading, error}` pair that can disagree with itself. */\nexport type ConversationStatus = 'idle' | 'connecting' | 'streaming' | AgentRunStatus;\n\nexport interface ConversationState {\n messages: AgentMessage[];\n streamingMessageId: string | null;\n status: ConversationStatus;\n /** Ephemeral chrome from a `status` event (\"Searching Dataverse…\"). Not message content. */\n statusLabel?: string;\n failure?: AgentFailure;\n usage?: AgentUsage;\n suggestions?: string[];\n}\n\nexport function initialConversation(seed: readonly AgentMessage[] = []): ConversationState {\n return { messages: [...seed], streamingMessageId: null, status: 'idle' };\n}\n\n/** Replace exactly one message, leaving every other array slot identity-stable so React can\n * bail out of re-rendering the rest of the transcript during a stream. */\nfunction patch(\n state: ConversationState,\n messageId: string,\n update: (m: AgentMessage) => AgentMessage,\n): AgentMessage[] {\n const i = state.messages.findIndex((m) => m.id === messageId);\n if (i === -1) return state.messages;\n const next = state.messages.slice();\n next[i] = update(next[i]);\n return next;\n}\n\nexport function appendUserMessage(\n state: ConversationState,\n input: { id: string; text: string; authorId?: string; attachments?: AgentAttachment[]; now?: number },\n): ConversationState {\n const message: AgentMessage = {\n id: input.id,\n role: 'user',\n authorId: input.authorId ?? 'user',\n text: input.text,\n createdAt: input.now ?? Date.now(),\n attachments: input.attachments,\n };\n return { ...state, messages: [...state.messages, message], status: 'connecting', failure: undefined };\n}\n\n/**\n * Apply one stream event.\n *\n * ★ Written as an EXHAUSTIVE switch with no `default:`. The repo's tsconfig sets\n * `noFallthroughCasesInSwitch` and `strict`, so adding a member to `AgentEvent` without a\n * case here is a compile error — a gate you get for free, and one that a tidy-looking\n * `default: return state` would silently remove. Do not add one.\n */\nexport function applyAgentEvent(state: ConversationState, event: AgentEvent): ConversationState {\n switch (event.type) {\n case 'turn_start': {\n const message: AgentMessage = {\n id: event.messageId,\n role: 'assistant',\n authorId: event.provider ?? 'agent',\n text: '',\n createdAt: Date.now(),\n streaming: true,\n provider: event.provider,\n };\n return {\n ...state,\n messages: [...state.messages, message],\n streamingMessageId: event.messageId,\n status: 'streaming',\n statusLabel: undefined,\n failure: undefined,\n suggestions: undefined,\n };\n }\n\n case 'text_delta':\n return {\n ...state,\n messages: patch(state, event.messageId, (m) => ({ ...m, text: m.text + event.text })),\n };\n\n case 'thinking_delta':\n return {\n ...state,\n messages: patch(state, event.messageId, (m) => ({\n ...m,\n thinking: (m.thinking ?? '') + event.text,\n })),\n };\n\n case 'tool_call':\n return {\n ...state,\n messages: patch(state, event.messageId, (m) => ({\n ...m,\n toolCalls: [...(m.toolCalls ?? []), event.call],\n })),\n };\n\n case 'tool_result':\n return {\n ...state,\n messages: patch(state, event.messageId, (m) => ({\n ...m,\n toolCalls: (m.toolCalls ?? []).map((c) =>\n c.id === event.callId\n ? { ...c, status: event.status, resultText: event.resultText, failure: event.failure }\n : c,\n ),\n })),\n };\n\n case 'sources':\n return {\n ...state,\n messages: patch(state, event.messageId, (m) => ({ ...m, sources: event.sources })),\n };\n\n case 'citation':\n return {\n ...state,\n messages: patch(state, event.messageId, (m) => ({\n ...m,\n citations: [...(m.citations ?? []), event.citation],\n })),\n };\n\n case 'suggestions':\n return { ...state, suggestions: event.prompts };\n\n case 'status':\n return { ...state, statusLabel: event.label };\n\n case 'turn_end': {\n const { result } = event;\n return {\n ...state,\n messages: patch(state, event.messageId, (m) => ({\n ...m,\n // A non-streaming provider sends no deltas, so the assembled text arrives here.\n text: m.text.length > 0 ? m.text : result.text,\n streaming: false,\n guarded: result.guarded,\n provider: result.provider,\n usage: result.usage,\n failure: result.failure,\n })),\n streamingMessageId: null,\n status: result.status,\n statusLabel: undefined,\n failure: result.failure,\n usage: result.usage,\n };\n }\n }\n}\n","import type { AgentEvent, AgentToolCall } from './protocol';\n\n/**\n * Transport normalizers: turn one provider's wire shape into `AgentEvent[]`.\n *\n * ★ Each returns an ARRAY, never a single event or null. One Azure OpenAI chunk can\n * legitimately carry a text delta AND a tool-call fragment AND a citation annotation, so\n * a one-in-one-out signature would silently drop two of the three.\n *\n * ★ These live in ./core (no React, no DOM) because a transport adapter frequently runs\n * where React must not be — an Azure Function proxying SSE, a web worker, a Node broker.\n * The same normalizer must be usable on both sides of that boundary.\n *\n * ★ Only shapes whose wire format is KNOWN are implemented. A normalizer written from an\n * assumed shape is a fixture that measures a platform which does not exist, so\n * `fromDirectLineActivity` is deliberately absent until an adapter exists to measure it\n * against — see CLAUDE.md.\n */\n\nexport interface NormalizeCtx {\n messageId: string;\n turnId: string;\n}\n\n/**\n * Unwrap one SSE line. Returns `undefined` for keep-alives, comments and blank lines, and\n * the sentinel `'[DONE]'` for the terminator — callers must distinguish those, since\n * \"nothing to do\" and \"the stream is over\" are different.\n */\nexport function fromSseLine(line: string): unknown | '[DONE]' | undefined {\n const trimmed = line.trim();\n if (trimmed === '' || trimmed.startsWith(':')) return undefined;\n if (!trimmed.startsWith('data:')) return undefined;\n const payload = trimmed.slice('data:'.length).trim();\n if (payload === '') return undefined;\n if (payload === '[DONE]') return '[DONE]';\n try {\n return JSON.parse(payload) as unknown;\n } catch {\n return undefined;\n }\n}\n\ninterface OpenAiDelta {\n content?: string | null;\n tool_calls?: { index?: number; id?: string; function?: { name?: string; arguments?: string } }[];\n}\ninterface OpenAiChunk {\n choices?: { delta?: OpenAiDelta }[];\n}\n\n/** An OpenAI / Azure OpenAI streaming chunk. */\nexport function fromOpenAiChatChunk(chunk: unknown, ctx: NormalizeCtx): AgentEvent[] {\n const events: AgentEvent[] = [];\n const delta = (chunk as OpenAiChunk | undefined)?.choices?.[0]?.delta;\n if (!delta) return events;\n\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n events.push({ type: 'text_delta', messageId: ctx.messageId, text: delta.content });\n }\n for (const call of delta.tool_calls ?? []) {\n if (!call.id) continue; // a pure arguments continuation; the caller accumulates it\n const toolCall: AgentToolCall = {\n id: call.id,\n name: call.function?.name ?? '',\n argumentsJson: call.function?.arguments ?? '',\n status: 'proposed',\n };\n events.push({ type: 'tool_call', messageId: ctx.messageId, call: toolCall });\n }\n return events;\n}\n\n/** The `@dataverse-kit/claude-bridge` event union. */\nexport interface ClaudeLikeStreamEvent {\n type: 'init' | 'text_delta' | 'thinking_delta' | 'done' | 'error';\n text?: string;\n fullText?: string;\n model?: string;\n sessionId?: string;\n costUsd?: number;\n durationMs?: number;\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n message?: string;\n recoverable?: boolean;\n}\n\nexport function fromClaudeStreamEvent(\n event: ClaudeLikeStreamEvent,\n ctx: NormalizeCtx,\n): AgentEvent[] {\n switch (event.type) {\n case 'init':\n return [\n {\n type: 'turn_start',\n turnId: ctx.turnId,\n messageId: ctx.messageId,\n model: event.model,\n provider: 'claude-bridge',\n sessionId: event.sessionId,\n },\n ];\n case 'text_delta':\n return event.text ? [{ type: 'text_delta', messageId: ctx.messageId, text: event.text }] : [];\n case 'thinking_delta':\n return event.text\n ? [{ type: 'thinking_delta', messageId: ctx.messageId, text: event.text }]\n : [];\n case 'done':\n return [\n {\n type: 'turn_end',\n turnId: ctx.turnId,\n messageId: ctx.messageId,\n result: {\n status: 'succeeded',\n text: event.fullText ?? '',\n provider: 'claude-bridge',\n model: event.model,\n usage: {\n inputTokens: event.inputTokens,\n outputTokens: event.outputTokens,\n cachedTokens: event.cacheReadTokens,\n costUsd: event.costUsd,\n durationMs: event.durationMs,\n model: event.model,\n },\n },\n },\n ];\n case 'error':\n return [\n {\n type: 'turn_end',\n turnId: ctx.turnId,\n messageId: ctx.messageId,\n result: {\n status: 'error',\n text: '',\n provider: 'claude-bridge',\n failure: {\n status: 'error',\n message: event.message ?? 'The agent failed.',\n recoverable: event.recoverable ?? false,\n },\n },\n },\n ];\n }\n}\n\n/** A NON-streaming Foundry reply (`certifications/ai-103/broker`'s `FoundryChatResult`). */\nexport interface FoundryLikeResult {\n text: string;\n promptTokens?: number;\n completionTokens?: number;\n model?: string;\n}\n\n/**\n * ★ Two events, no faked deltas. This is what `AgentTurnResult.text` exists for: a provider\n * with no streaming at all still produces a complete message through the same reducer.\n */\nexport function fromFoundryChatResult(\n result: FoundryLikeResult,\n ctx: NormalizeCtx,\n provider = 'foundry',\n): AgentEvent[] {\n return [\n { type: 'turn_start', turnId: ctx.turnId, messageId: ctx.messageId, model: result.model, provider },\n {\n type: 'turn_end',\n turnId: ctx.turnId,\n messageId: ctx.messageId,\n result: {\n status: 'succeeded',\n text: result.text,\n provider,\n model: result.model,\n usage: {\n inputTokens: result.promptTokens,\n outputTokens: result.completionTokens,\n model: result.model,\n },\n },\n },\n ];\n}\n","import type { ConversationState } from './conversationReducer';\n\n/** Past this many characters, announce a summary instead of the whole answer. */\nexport const ANNOUNCE_FULL_TEXT_MAX_CHARS = 240;\n\nexport interface Announcement {\n /** Read when the screen reader next pauses. Empty means \"nothing new\". */\n polite: string;\n /** Interrupts. Reserved for failures. */\n assertive: string;\n}\n\nexport const NO_ANNOUNCEMENT: Announcement = { polite: '', assertive: '' };\n\nconst TERMINAL = new Set(['succeeded', 'blocked', 'refused', 'error', 'notProvisioned', 'cancelled']);\n\n/**\n * What, if anything, should be announced for this state change.\n *\n * ★★ THE WHOLE POINT: this is derived from a state TRANSITION, never from a delta.\n *\n * Both naive choices are wrong. No live region at all and a screen-reader user never\n * hears the answer — the kit's primary output is silent. A live region on the streaming\n * node and every token is queued for announcement; NVDA and JAWS re-read the growing\n * string and never finish. So the transcript is NOT a live region, the streaming bubble\n * carries `aria-busy`, the typing indicator's own `role=\"status\"` covers \"responding\",\n * and this announces exactly once per turn: the settled answer.\n *\n * ★ Deltas, `thinking` and tool calls are never announced. Thinking especially: it is\n * explicitly the model's scratch work, and reading it aloud would bury the answer.\n *\n * ★ Failures go to the ASSERTIVE channel, because an error the user is not told about is\n * an error they wait through.\n */\nexport function deriveAnnouncement(\n state: ConversationState,\n previous: ConversationState | undefined,\n): Announcement {\n const wasTerminal = previous ? TERMINAL.has(previous.status) : false;\n const isTerminal = TERMINAL.has(state.status);\n\n // Only the moment of settling produces an announcement.\n if (!isTerminal || wasTerminal) return NO_ANNOUNCEMENT;\n\n if (state.failure) {\n return { polite: '', assertive: state.failure.message };\n }\n\n const last = state.messages[state.messages.length - 1];\n const text = last?.text ?? '';\n if (!text) return NO_ANNOUNCEMENT;\n\n const guarded = last?.guarded ? 'The response was withheld. ' : '';\n if (text.length <= ANNOUNCE_FULL_TEXT_MAX_CHARS) {\n return { polite: `${guarded}${text}`, assertive: '' };\n }\n const words = text.trim().split(/\\s+/).length;\n return {\n polite: `${guarded}Response complete, ${words} words.`,\n assertive: '',\n };\n}\n","/** How close to the bottom still counts as \"at the bottom\", in CSS pixels. */\nexport const STICK_TO_BOTTOM_THRESHOLD_PX = 32;\n\nexport interface ScrollGeometry {\n scrollTop: number;\n scrollHeight: number;\n clientHeight: number;\n}\n\n/**\n * Should an append scroll the transcript to the bottom?\n *\n * ★ The DECISION is extracted from the EFFECT on purpose. It is what lets a unit test\n * express \"the user scrolled up, so do not yank them back\" without a real scroller —\n * jsdom reports `scrollHeight`/`clientHeight` as 0 forever, so a test that drives a real\n * element can only ever observe \"at the bottom\" and the interesting branch never runs.\n *\n * ★ The rule itself carries a MEASURED defect forward. apps/form-builder's ChatPane once\n * keyed its scroll effect on the streaming message's content length, which queued a\n * smooth `scrollTo` per token — 10+ per second — and jittered the viewport. The fix there\n * was to key on message COUNT and use `behavior: 'auto'`. This adds the half that was\n * still missing: only stick if the reader was already at the bottom, so scrolling up to\n * re-read mid-stream is not undone.\n */\nexport function shouldStickToBottom(\n geometry: ScrollGeometry,\n thresholdPx: number = STICK_TO_BOTTOM_THRESHOLD_PX,\n): boolean {\n const { scrollTop, scrollHeight, clientHeight } = geometry;\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n return distanceFromBottom <= thresholdPx;\n}\n","/**\n * Build a per-instance id-prefix generator.\n *\n * ★ The FACTORY is exported (pure, testable); the module SINGLETON that a provider uses\n * lives in src/host/idPrefix.ts. That split matters: the gallery's measurement is that\n * there must be exactly ONE counter per bundle, so two instances of the same control get\n * different prefixes. A constant prefix is the wrong fix and a per-call factory is too.\n */\nexport function createIdPrefixCounter(tag: string): () => string {\n let n = 0;\n return () => `${tag}${++n}-`;\n}\n"],"mappings":"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","names":["dayKey","TERMINAL"]}
|
package/dist/host.cjs
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/host/index.ts
|
|
31
|
+
var host_exports = {};
|
|
32
|
+
__export(host_exports, {
|
|
33
|
+
AgentKitHostProvider: () => AgentKitHostProvider,
|
|
34
|
+
agentDarkTheme: () => agentDarkTheme,
|
|
35
|
+
agentTheme: () => agentTheme,
|
|
36
|
+
createAgentDarkTheme: () => createAgentDarkTheme,
|
|
37
|
+
createAgentTheme: () => createAgentTheme,
|
|
38
|
+
nextAgentIdPrefix: () => nextAgentIdPrefix,
|
|
39
|
+
resolveHostTheme: () => resolveHostTheme
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(host_exports);
|
|
42
|
+
|
|
43
|
+
// src/host/AgentKitHostProvider.tsx
|
|
44
|
+
var React = __toESM(require("react"), 1);
|
|
45
|
+
var import_react_components = require("@fluentui/react-components");
|
|
46
|
+
|
|
47
|
+
// src/theme/index.ts
|
|
48
|
+
var import_fluent_theme = require("@dataverse-kit/fluent-theme");
|
|
49
|
+
var import_design_tokens = require("@dataverse-kit/design-tokens");
|
|
50
|
+
function applyAgentOverrides(base) {
|
|
51
|
+
return {
|
|
52
|
+
...base,
|
|
53
|
+
// Fluent's web theme is close to CE but does not pin the Web variant of Segoe.
|
|
54
|
+
fontFamilyBase: import_design_tokens.typography.fontFamily,
|
|
55
|
+
// CE is squarer than Fluent web's default. Bubbles opt back up to borderRadiusLarge.
|
|
56
|
+
borderRadiusMedium: import_design_tokens.radius.card
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function createAgentTheme(brand = import_fluent_theme.dynamicsBrandRamp) {
|
|
60
|
+
return applyAgentOverrides((0, import_fluent_theme.createDynamicsV9Theme)(brand));
|
|
61
|
+
}
|
|
62
|
+
function createAgentDarkTheme(brand = import_fluent_theme.dynamicsBrandRamp) {
|
|
63
|
+
return applyAgentOverrides((0, import_fluent_theme.createDynamicsV9DarkTheme)(brand));
|
|
64
|
+
}
|
|
65
|
+
var agentTheme = createAgentTheme();
|
|
66
|
+
var agentDarkTheme = createAgentDarkTheme();
|
|
67
|
+
function resolveHostTheme(input = {}) {
|
|
68
|
+
if (input.theme) return input.theme;
|
|
69
|
+
if (input.hostTheme) return input.hostTheme;
|
|
70
|
+
return input.isDarkTheme ? agentDarkTheme : agentTheme;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/core/ids.ts
|
|
74
|
+
function createIdPrefixCounter(tag) {
|
|
75
|
+
let n = 0;
|
|
76
|
+
return () => `${tag}${++n}-`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/host/idPrefix.ts
|
|
80
|
+
var nextAgentIdPrefix = createIdPrefixCounter("dvkak");
|
|
81
|
+
|
|
82
|
+
// src/host/AgentKitHostProvider.tsx
|
|
83
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
84
|
+
function AgentKitHostProvider(props) {
|
|
85
|
+
const { children, theme, hostTheme, isDarkTheme, targetDocument, className, style } = props;
|
|
86
|
+
const [generated] = React.useState(nextAgentIdPrefix);
|
|
87
|
+
const idPrefix = props.idPrefix ?? generated;
|
|
88
|
+
const resolved = React.useMemo(
|
|
89
|
+
() => resolveHostTheme({ theme, hostTheme, isDarkTheme }),
|
|
90
|
+
[theme, hostTheme, isDarkTheme]
|
|
91
|
+
);
|
|
92
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_components.IdPrefixProvider, { value: idPrefix, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
93
|
+
import_react_components.FluentProvider,
|
|
94
|
+
{
|
|
95
|
+
theme: resolved,
|
|
96
|
+
targetDocument,
|
|
97
|
+
className,
|
|
98
|
+
style,
|
|
99
|
+
children
|
|
100
|
+
}
|
|
101
|
+
) });
|
|
102
|
+
}
|
|
103
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
104
|
+
0 && (module.exports = {
|
|
105
|
+
AgentKitHostProvider,
|
|
106
|
+
agentDarkTheme,
|
|
107
|
+
agentTheme,
|
|
108
|
+
createAgentDarkTheme,
|
|
109
|
+
createAgentTheme,
|
|
110
|
+
nextAgentIdPrefix,
|
|
111
|
+
resolveHostTheme
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=host.cjs.map
|