assistant-cloud
Version:
Cloud integration for assistant-ui
134 lines (133 loc) • 5.01 kB
JavaScript
// src/CloudEngagementReporter.ts
const MAX_REMEMBERED_THREADS = 256;
function remember(map, threadId, value) {
map.delete(threadId);
map.set(threadId, value);
if (map.size > MAX_REMEMBERED_THREADS) map.delete(map.keys().next().value);
}
function mark(set, threadId) {
set.delete(threadId);
set.add(threadId);
if (set.size > MAX_REMEMBERED_THREADS) set.delete(set.values().next().value);
}
const passThroughIds = (threadId, messageId) => ({
thread_id: threadId,
...messageId !== void 0 ? { message_id: messageId } : void 0
});
/**
* Derives engagement events from what a chat integration observes and keeps
* the per thread state the events need: a run's start for the stop duration,
* a run's end for the time to the next message, one error and one suggestion
* list per run or thread. A started run is kept until it ends or stops; the
* rest is kept for the 256 most recently touched threads, so a long session
* does not grow it without bound. Delivery goes through the cloud's event
* buffer, so a disabled telemetry setting drops everything here as well.
*/
var CloudEngagementReporter = class {
runStartedAt = /* @__PURE__ */ new Map();
runEndedAt = /* @__PURE__ */ new Map();
shownErrors = /* @__PURE__ */ new Set();
shownSuggestions = /* @__PURE__ */ new Set();
getCloud;
resolveIds;
constructor(cloud, resolveIds = passThroughIds) {
this.getCloud = typeof cloud === "function" ? cloud : () => cloud;
this.resolveIds = resolveIds;
}
runStarted(threadId) {
this.runStartedAt.set(threadId, Date.now());
this.shownErrors.delete(threadId);
}
runEnded(threadId) {
this.runStartedAt.delete(threadId);
remember(this.runEndedAt, threadId, Date.now());
}
/** Reported once per started run, with the time the run had been going. */
runStopped(threadId) {
const startedAt = this.runStartedAt.get(threadId);
if (startedAt === void 0) return;
const stoppedAt = Date.now();
this.runStartedAt.delete(threadId);
remember(this.runEndedAt, threadId, stoppedAt);
this.track("run_stopped", threadId, void 0, { value: Math.max(0, stoppedAt - startedAt) });
}
/** Carries the time since the previous run of the thread ended, when known. */
messageSent(threadId, init) {
const previousRunEndedAt = this.runEndedAt.get(threadId);
this.track("message_sent", threadId, init.messageId, {
props: {
chars: init.chars,
attachments: init.attachments
},
...previousRunEndedAt !== void 0 ? { value: Math.max(0, Date.now() - previousRunEndedAt) } : void 0
}, { awaitThread: true });
}
messageEdited(threadId, init) {
this.track("message_edited", threadId, init.messageId, { props: { chars: init.chars } });
}
messageRegenerated(threadId, messageId) {
this.track("message_regenerated", threadId, messageId);
}
/** Reported once per run, so a retried render of the same error stays one event. */
errorShown(threadId, init) {
if (this.shownErrors.has(threadId)) return;
mark(this.shownErrors, threadId);
this.track("error_shown", threadId, init.messageId, { props: { reason: init.reason } });
}
/** Reported once per thread, with the number of suggestions on offer. */
suggestionsShown(threadId, count) {
if (this.shownSuggestions.has(threadId)) return;
mark(this.shownSuggestions, threadId);
this.track("suggestions_shown", threadId, void 0, { value: count });
}
suggestionClicked(threadId) {
this.track("suggestion_clicked", threadId);
}
attachmentAdded(threadId, init) {
this.track("attachment_added", threadId, init.messageId, { ...init.contentType ? { props: { type: init.contentType } } : void 0 });
}
attachmentFailed(threadId, init) {
this.track("attachment_failed", threadId, init.messageId, { ...init.contentType ? { props: { type: init.contentType } } : void 0 });
}
voiceStarted(threadId) {
this.track("voice_started", threadId);
}
speechStarted(threadId, messageId) {
this.track("speech_started", threadId, messageId);
}
branchSwitched(threadId, messageId) {
this.track("branch_switched", threadId, messageId);
}
messageCopied(threadId, messageId) {
this.track("message_copied", threadId, messageId);
}
toolApproved(threadId, messageId, toolCallId, toolName) {
this.track("tool_approved", threadId, messageId, { props: {
toolCallId,
toolName
} });
}
toolRejected(threadId, messageId, toolCallId, toolName) {
this.track("tool_rejected", threadId, messageId, { props: {
toolCallId,
toolName
} });
}
/** Reported only for a thread the cloud already knows. */
threadSwitched(threadId) {
this.track("thread_switched", threadId);
}
track(kind, threadId, messageId, init = {}, options = { awaitThread: false }) {
Promise.resolve().then(() => this.resolveIds(threadId, messageId, options)).then((ids) => {
if (!ids) return;
if (kind === "thread_switched" && !ids.thread_id) return;
this.getCloud().events.track({
kind,
...init,
...ids
});
}).catch(() => {});
}
};
export { CloudEngagementReporter };
//# sourceMappingURL=CloudEngagementReporter.js.map