@openattribution/telemetry 0.1.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/LICENSE +190 -0
- package/README.md +243 -0
- package/dist/index.cjs +442 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +440 -0
- package/dist/index.d.ts +440 -0
- package/dist/index.js +410 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MCPSessionTracker: () => MCPSessionTracker,
|
|
24
|
+
TelemetryClient: () => TelemetryClient,
|
|
25
|
+
extractCitationUrls: () => extractCitationUrls,
|
|
26
|
+
extractResultUrls: () => extractResultUrls,
|
|
27
|
+
sessionToAttribution: () => sessionToAttribution,
|
|
28
|
+
sessionToContentAttribution: () => sessionToContentAttribution
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/client.ts
|
|
33
|
+
var TRANSIENT_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
34
|
+
function turnToWire(turn) {
|
|
35
|
+
return {
|
|
36
|
+
privacy_level: turn.privacyLevel,
|
|
37
|
+
query_text: turn.queryText,
|
|
38
|
+
response_text: turn.responseText,
|
|
39
|
+
query_intent: turn.queryIntent,
|
|
40
|
+
response_type: turn.responseType,
|
|
41
|
+
topics: turn.topics,
|
|
42
|
+
content_urls_retrieved: turn.contentUrlsRetrieved,
|
|
43
|
+
content_urls_cited: turn.contentUrlsCited,
|
|
44
|
+
query_tokens: turn.queryTokens,
|
|
45
|
+
response_tokens: turn.responseTokens,
|
|
46
|
+
model_id: turn.modelId
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function eventToWire(event) {
|
|
50
|
+
return {
|
|
51
|
+
id: event.id,
|
|
52
|
+
type: event.type,
|
|
53
|
+
timestamp: event.timestamp,
|
|
54
|
+
content_url: event.contentUrl,
|
|
55
|
+
product_id: event.productId,
|
|
56
|
+
turn: event.turn != null ? turnToWire(event.turn) : void 0,
|
|
57
|
+
data: event.data ?? {}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function initiatorToWire(i) {
|
|
61
|
+
return {
|
|
62
|
+
agent_id: i.agentId,
|
|
63
|
+
manifest_ref: i.manifestRef,
|
|
64
|
+
operator_id: i.operatorId
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function userContextToWire(uc) {
|
|
68
|
+
return {
|
|
69
|
+
external_id: uc.externalId,
|
|
70
|
+
segments: uc.segments ?? [],
|
|
71
|
+
attributes: uc.attributes ?? {}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function outcomeToWire(o) {
|
|
75
|
+
return {
|
|
76
|
+
type: o.type,
|
|
77
|
+
value_amount: o.valueAmount ?? 0,
|
|
78
|
+
currency: o.currency ?? "USD",
|
|
79
|
+
products: o.products ?? [],
|
|
80
|
+
metadata: o.metadata ?? {}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
var TelemetryClient = class {
|
|
84
|
+
constructor(options) {
|
|
85
|
+
this.endpoint = options.endpoint.replace(/\/$/, "");
|
|
86
|
+
this.apiKey = options.apiKey;
|
|
87
|
+
this.failSilently = options.failSilently ?? true;
|
|
88
|
+
this.timeout = options.timeout ?? 3e4;
|
|
89
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
90
|
+
}
|
|
91
|
+
headers() {
|
|
92
|
+
const h = { "Content-Type": "application/json" };
|
|
93
|
+
if (this.apiKey != null) h["X-API-Key"] = this.apiKey;
|
|
94
|
+
return h;
|
|
95
|
+
}
|
|
96
|
+
async post(path, body) {
|
|
97
|
+
const url = `${this.endpoint}${path}`;
|
|
98
|
+
let lastError;
|
|
99
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch(url, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: this.headers(),
|
|
106
|
+
body: JSON.stringify(body),
|
|
107
|
+
signal: controller.signal
|
|
108
|
+
});
|
|
109
|
+
if (TRANSIENT_STATUS_CODES.has(res.status) && attempt < this.maxRetries) {
|
|
110
|
+
const wait = 2 ** attempt * 1e3 + Math.random() * 500;
|
|
111
|
+
await sleep(wait);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
throw new Error(`HTTP ${res.status} ${res.statusText} from ${url}`);
|
|
116
|
+
}
|
|
117
|
+
return await res.json();
|
|
118
|
+
} catch (err) {
|
|
119
|
+
lastError = err;
|
|
120
|
+
if (attempt < this.maxRetries && isTransientError(err)) {
|
|
121
|
+
const wait = 2 ** attempt * 1e3 + Math.random() * 500;
|
|
122
|
+
await sleep(wait);
|
|
123
|
+
} else {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (this.failSilently) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
throw lastError;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Start a new telemetry session.
|
|
137
|
+
*
|
|
138
|
+
* @returns Session ID string, or null on silent failure.
|
|
139
|
+
*/
|
|
140
|
+
async startSession(options = {}) {
|
|
141
|
+
const result = await this.post("/session/start", {
|
|
142
|
+
initiator_type: options.initiatorType ?? "user",
|
|
143
|
+
initiator: options.initiator != null ? initiatorToWire(options.initiator) : null,
|
|
144
|
+
content_scope: options.contentScope,
|
|
145
|
+
agent_id: options.agentId,
|
|
146
|
+
external_session_id: options.externalSessionId,
|
|
147
|
+
user_context: options.userContext != null ? userContextToWire(options.userContext) : {},
|
|
148
|
+
manifest_ref: options.manifestRef,
|
|
149
|
+
prior_session_ids: options.priorSessionIds ?? []
|
|
150
|
+
});
|
|
151
|
+
return result?.session_id ?? null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Record a single telemetry event.
|
|
155
|
+
*/
|
|
156
|
+
async recordEvent(sessionId, eventType, options = {}) {
|
|
157
|
+
if (sessionId == null) return;
|
|
158
|
+
await this.recordEvents(sessionId, [
|
|
159
|
+
{
|
|
160
|
+
id: crypto.randomUUID(),
|
|
161
|
+
type: eventType,
|
|
162
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
163
|
+
...options
|
|
164
|
+
}
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Record a batch of telemetry events.
|
|
169
|
+
*/
|
|
170
|
+
async recordEvents(sessionId, events) {
|
|
171
|
+
if (sessionId == null || events.length === 0) return;
|
|
172
|
+
await this.post("/events", {
|
|
173
|
+
session_id: sessionId,
|
|
174
|
+
events: events.map(eventToWire)
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* End a session with an outcome.
|
|
179
|
+
*/
|
|
180
|
+
async endSession(sessionId, outcome) {
|
|
181
|
+
if (sessionId == null) return;
|
|
182
|
+
await this.post("/session/end", {
|
|
183
|
+
session_id: sessionId,
|
|
184
|
+
outcome: outcomeToWire(outcome)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Upload a complete session in one request (bulk path).
|
|
189
|
+
*
|
|
190
|
+
* Useful for post-hoc reporting or when you've built the session
|
|
191
|
+
* locally and want to submit it in one shot.
|
|
192
|
+
*
|
|
193
|
+
* @returns Server-assigned session ID, or null on silent failure.
|
|
194
|
+
*/
|
|
195
|
+
async uploadSession(session) {
|
|
196
|
+
const result = await this.post("/session/bulk", sessionToWire(session));
|
|
197
|
+
return result?.session_id ?? null;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
function sleep(ms) {
|
|
201
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
202
|
+
}
|
|
203
|
+
function isTransientError(err) {
|
|
204
|
+
if (err instanceof Error) {
|
|
205
|
+
return err.name === "AbortError" || err.name === "TypeError" || err.message.includes("fetch");
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
function sessionToWire(session) {
|
|
210
|
+
return {
|
|
211
|
+
schema_version: session.schemaVersion ?? "0.4",
|
|
212
|
+
session_id: session.sessionId,
|
|
213
|
+
initiator_type: session.initiatorType ?? "user",
|
|
214
|
+
initiator: session.initiator != null ? initiatorToWire(session.initiator) : null,
|
|
215
|
+
agent_id: session.agentId,
|
|
216
|
+
content_scope: session.contentScope,
|
|
217
|
+
manifest_ref: session.manifestRef,
|
|
218
|
+
prior_session_ids: session.priorSessionIds ?? [],
|
|
219
|
+
started_at: session.startedAt,
|
|
220
|
+
ended_at: session.endedAt,
|
|
221
|
+
user_context: session.userContext != null ? userContextToWire(session.userContext) : {},
|
|
222
|
+
events: session.events.map(eventToWire),
|
|
223
|
+
outcome: session.outcome != null ? outcomeToWire(session.outcome) : void 0
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/mcp.ts
|
|
228
|
+
var MCPSessionTracker = class {
|
|
229
|
+
/**
|
|
230
|
+
* @param client - Configured TelemetryClient instance.
|
|
231
|
+
* @param contentScope - Stable identifier for this agent's content scope
|
|
232
|
+
* (e.g. mix ID, manifest reference, or descriptive slug like "my-shopping-agent").
|
|
233
|
+
*/
|
|
234
|
+
constructor(client, contentScope = "mcp-agent") {
|
|
235
|
+
this.registry = /* @__PURE__ */ new Map();
|
|
236
|
+
this.client = client;
|
|
237
|
+
this.contentScope = contentScope;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get or create an OA session for the given external session ID.
|
|
241
|
+
*
|
|
242
|
+
* If `externalSessionId` is undefined, a new anonymous session is created
|
|
243
|
+
* on every call (no continuity across tool calls).
|
|
244
|
+
*
|
|
245
|
+
* @returns OA session ID string, or null on silent failure.
|
|
246
|
+
*/
|
|
247
|
+
async getOrCreateSession(externalSessionId) {
|
|
248
|
+
if (externalSessionId != null && this.registry.has(externalSessionId)) {
|
|
249
|
+
return this.registry.get(externalSessionId) ?? null;
|
|
250
|
+
}
|
|
251
|
+
const sessionId = await this.client.startSession({
|
|
252
|
+
contentScope: this.contentScope,
|
|
253
|
+
...externalSessionId != null && { externalSessionId }
|
|
254
|
+
});
|
|
255
|
+
if (externalSessionId != null) {
|
|
256
|
+
this.registry.set(externalSessionId, sessionId);
|
|
257
|
+
}
|
|
258
|
+
return sessionId;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Emit `content_retrieved` events for a list of URLs.
|
|
262
|
+
*
|
|
263
|
+
* Call this after fetching products, search results, or any content
|
|
264
|
+
* that influenced the agent's response.
|
|
265
|
+
*
|
|
266
|
+
* @param externalSessionId - Caller-supplied conversation identifier.
|
|
267
|
+
* @param urls - URLs of content retrieved during this tool call.
|
|
268
|
+
*/
|
|
269
|
+
async trackRetrieved(externalSessionId, urls) {
|
|
270
|
+
if (urls.length === 0) return;
|
|
271
|
+
const sessionId = await this.getOrCreateSession(externalSessionId);
|
|
272
|
+
if (sessionId == null) return;
|
|
273
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
274
|
+
const events = urls.map((url) => ({
|
|
275
|
+
id: crypto.randomUUID(),
|
|
276
|
+
type: "content_retrieved",
|
|
277
|
+
timestamp: now,
|
|
278
|
+
contentUrl: url
|
|
279
|
+
}));
|
|
280
|
+
await this.client.recordEvents(sessionId, events);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Emit `content_cited` events for content explicitly referenced in a response.
|
|
284
|
+
*
|
|
285
|
+
* Call this when you know which content the agent cited — e.g. the top
|
|
286
|
+
* search result, an editorial quote, or a product recommendation.
|
|
287
|
+
*
|
|
288
|
+
* @param externalSessionId - Caller-supplied conversation identifier.
|
|
289
|
+
* @param urls - URLs of content cited in the agent's response.
|
|
290
|
+
* @param options - Optional citation metadata.
|
|
291
|
+
*/
|
|
292
|
+
async trackCited(externalSessionId, urls, options = {}) {
|
|
293
|
+
if (urls.length === 0) return;
|
|
294
|
+
const sessionId = await this.getOrCreateSession(externalSessionId);
|
|
295
|
+
if (sessionId == null) return;
|
|
296
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
297
|
+
const events = urls.map((url) => ({
|
|
298
|
+
id: crypto.randomUUID(),
|
|
299
|
+
type: "content_cited",
|
|
300
|
+
timestamp: now,
|
|
301
|
+
contentUrl: url,
|
|
302
|
+
data: {
|
|
303
|
+
...options.citationType != null && { citation_type: options.citationType },
|
|
304
|
+
...options.position != null && { position: options.position }
|
|
305
|
+
}
|
|
306
|
+
}));
|
|
307
|
+
await this.client.recordEvents(sessionId, events);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* End a session with an outcome.
|
|
311
|
+
*
|
|
312
|
+
* Call this when the conversation concludes — at checkout, after
|
|
313
|
+
* the user clicks a link, or when the session times out.
|
|
314
|
+
*/
|
|
315
|
+
async endSession(externalSessionId, outcome) {
|
|
316
|
+
const sessionId = externalSessionId != null ? this.registry.get(externalSessionId) ?? null : null;
|
|
317
|
+
if (sessionId == null) return;
|
|
318
|
+
await this.client.endSession(sessionId, {
|
|
319
|
+
type: outcome.type,
|
|
320
|
+
...outcome.valueAmount != null && { valueAmount: outcome.valueAmount },
|
|
321
|
+
...outcome.currency != null && { currency: outcome.currency }
|
|
322
|
+
});
|
|
323
|
+
this.registry.delete(externalSessionId);
|
|
324
|
+
}
|
|
325
|
+
/** Number of active sessions in the registry. */
|
|
326
|
+
get sessionCount() {
|
|
327
|
+
return this.registry.size;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// src/extract.ts
|
|
332
|
+
var MARKDOWN_LINK_RE = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
|
|
333
|
+
var BARE_URL_RE = /https?:\/\/[^\s<>"')\]]+/g;
|
|
334
|
+
function extractCitationUrls(text) {
|
|
335
|
+
const urls = /* @__PURE__ */ new Set();
|
|
336
|
+
for (const match of text.matchAll(MARKDOWN_LINK_RE)) {
|
|
337
|
+
const url = match[2];
|
|
338
|
+
if (url != null) urls.add(cleanUrl(url));
|
|
339
|
+
}
|
|
340
|
+
for (const match of text.matchAll(BARE_URL_RE)) {
|
|
341
|
+
urls.add(cleanUrl(match[0]));
|
|
342
|
+
}
|
|
343
|
+
return [...urls];
|
|
344
|
+
}
|
|
345
|
+
function extractResultUrls(results) {
|
|
346
|
+
const urls = [];
|
|
347
|
+
for (const r of results) {
|
|
348
|
+
const url = r.url ?? r.link;
|
|
349
|
+
if (url != null && url.startsWith("http")) {
|
|
350
|
+
urls.push(url);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return [...new Set(urls)];
|
|
354
|
+
}
|
|
355
|
+
function cleanUrl(url) {
|
|
356
|
+
return url.replace(/[.,;:!?]+$/, "");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/acp.ts
|
|
360
|
+
function sessionToContentAttribution(session) {
|
|
361
|
+
const retrieved = session.events.filter((e) => e.type === "content_retrieved" && e.contentUrl != null).map((e) => ({
|
|
362
|
+
content_url: e.contentUrl,
|
|
363
|
+
timestamp: e.timestamp
|
|
364
|
+
}));
|
|
365
|
+
const cited = session.events.filter((e) => e.type === "content_cited" && e.contentUrl != null).map((e) => ({
|
|
366
|
+
content_url: e.contentUrl,
|
|
367
|
+
timestamp: e.timestamp,
|
|
368
|
+
...e.data?.["citation_type"] != null && {
|
|
369
|
+
citation_type: String(e.data["citation_type"])
|
|
370
|
+
},
|
|
371
|
+
...e.data?.["excerpt_tokens"] != null && {
|
|
372
|
+
excerpt_tokens: Number(e.data["excerpt_tokens"])
|
|
373
|
+
},
|
|
374
|
+
...e.data?.["position"] != null && {
|
|
375
|
+
position: String(e.data["position"])
|
|
376
|
+
},
|
|
377
|
+
...e.data?.["content_hash"] != null && {
|
|
378
|
+
content_hash: String(e.data["content_hash"])
|
|
379
|
+
}
|
|
380
|
+
}));
|
|
381
|
+
const turnEvents = session.events.filter(
|
|
382
|
+
(e) => e.type === "turn_completed" || e.type === "turn_started"
|
|
383
|
+
);
|
|
384
|
+
const turnCount = Math.max(
|
|
385
|
+
session.events.filter((e) => e.type === "turn_completed").length,
|
|
386
|
+
1
|
|
387
|
+
);
|
|
388
|
+
const topics = [
|
|
389
|
+
...new Set(
|
|
390
|
+
session.events.flatMap((e) => e.turn?.topics ?? [])
|
|
391
|
+
)
|
|
392
|
+
];
|
|
393
|
+
const result = {
|
|
394
|
+
...session.contentScope != null && { content_scope: session.contentScope },
|
|
395
|
+
content_retrieved: retrieved,
|
|
396
|
+
...cited.length > 0 && { content_cited: cited },
|
|
397
|
+
...(turnEvents.length > 0 || topics.length > 0) && {
|
|
398
|
+
conversation_summary: { turn_count: turnCount, topics }
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/ucp.ts
|
|
405
|
+
function sessionToAttribution(session) {
|
|
406
|
+
const retrieved = session.events.filter((e) => e.type === "content_retrieved" && e.contentUrl != null).map((e) => ({ content_url: e.contentUrl, timestamp: e.timestamp }));
|
|
407
|
+
const cited = session.events.filter((e) => e.type === "content_cited" && e.contentUrl != null).map((e) => ({
|
|
408
|
+
content_url: e.contentUrl,
|
|
409
|
+
timestamp: e.timestamp,
|
|
410
|
+
...e.data?.["citation_type"] != null && {
|
|
411
|
+
citation_type: String(e.data["citation_type"])
|
|
412
|
+
},
|
|
413
|
+
...e.data?.["position"] != null && {
|
|
414
|
+
position: String(e.data["position"])
|
|
415
|
+
}
|
|
416
|
+
}));
|
|
417
|
+
const turnCount = Math.max(
|
|
418
|
+
session.events.filter((e) => e.type === "turn_completed").length,
|
|
419
|
+
1
|
|
420
|
+
);
|
|
421
|
+
const topics = [...new Set(session.events.flatMap((e) => e.turn?.topics ?? []))];
|
|
422
|
+
const priorIds = (session.priorSessionIds ?? []).filter(Boolean);
|
|
423
|
+
return {
|
|
424
|
+
...session.contentScope != null && { content_scope: session.contentScope },
|
|
425
|
+
...priorIds.length > 0 && { prior_session_ids: priorIds },
|
|
426
|
+
content_retrieved: retrieved,
|
|
427
|
+
...cited.length > 0 && { content_cited: cited },
|
|
428
|
+
...(topics.length > 0 || turnCount > 0) && {
|
|
429
|
+
conversation_summary: { turn_count: turnCount, topics }
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
434
|
+
0 && (module.exports = {
|
|
435
|
+
MCPSessionTracker,
|
|
436
|
+
TelemetryClient,
|
|
437
|
+
extractCitationUrls,
|
|
438
|
+
extractResultUrls,
|
|
439
|
+
sessionToAttribution,
|
|
440
|
+
sessionToContentAttribution
|
|
441
|
+
});
|
|
442
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/mcp.ts","../src/extract.ts","../src/acp.ts","../src/ucp.ts"],"sourcesContent":["/**\n * @openattribution/telemetry\n *\n * OpenAttribution Telemetry SDK for TypeScript/JavaScript.\n * Track content attribution in AI agent interactions.\n *\n * Specification: https://openattribution.org/telemetry\n *\n * @example\n * ```ts\n * import { TelemetryClient, MCPSessionTracker, extractCitationUrls } from \"@openattribution/telemetry\";\n *\n * // Direct client usage\n * const client = new TelemetryClient({\n * endpoint: \"https://telemetry.example.com\",\n * apiKey: process.env.TELEMETRY_API_KEY,\n * failSilently: true,\n * });\n *\n * // MCP agent usage\n * const tracker = new MCPSessionTracker(client, \"my-shopping-agent\");\n * await tracker.trackRetrieved(sessionId, productUrls);\n *\n * // Extract citation URLs from AI response text\n * const urls = extractCitationUrls(assistantMessage);\n * await tracker.trackCited(sessionId, urls);\n * ```\n */\n\nexport { TelemetryClient } from \"./client.js\";\nexport { MCPSessionTracker } from \"./mcp.js\";\nexport { extractCitationUrls, extractResultUrls } from \"./extract.js\";\nexport { sessionToContentAttribution } from \"./acp.js\";\nexport { sessionToAttribution } from \"./ucp.js\";\n\nexport type {\n // Types\n TelemetryClientOptions,\n TelemetrySession,\n TelemetryEvent,\n SessionOutcome,\n StartSessionOptions,\n ConversationTurn,\n UserContext,\n Initiator,\n // Enumerations\n EventType,\n OutcomeType,\n PrivacyLevel,\n IntentCategory,\n InitiatorType,\n CitationType,\n CitationPosition,\n} from \"./types.js\";\n\nexport type { ContentAttribution, ContentAttributionRetrieved, ContentAttributionCited } from \"./acp.js\";\nexport type { UCPAttribution } from \"./ucp.js\";\n","/**\n * OpenAttribution Telemetry — HTTP client.\n *\n * Zero dependencies — uses native fetch (Node 18+, Deno, browsers, Edge).\n */\n\nimport type {\n ConversationTurn,\n EventType,\n Initiator,\n SessionOutcome,\n StartSessionOptions,\n TelemetryClientOptions,\n TelemetryEvent,\n TelemetrySession,\n UserContext,\n} from \"./types.js\";\n\nconst TRANSIENT_STATUS_CODES = new Set([429, 500, 502, 503, 504]);\n\n// ---------------------------------------------------------------------------\n// Wire format helpers (camelCase → snake_case for the JSON body)\n// ---------------------------------------------------------------------------\n\nfunction turnToWire(turn: ConversationTurn): Record<string, unknown> {\n return {\n privacy_level: turn.privacyLevel,\n query_text: turn.queryText,\n response_text: turn.responseText,\n query_intent: turn.queryIntent,\n response_type: turn.responseType,\n topics: turn.topics,\n content_urls_retrieved: turn.contentUrlsRetrieved,\n content_urls_cited: turn.contentUrlsCited,\n query_tokens: turn.queryTokens,\n response_tokens: turn.responseTokens,\n model_id: turn.modelId,\n };\n}\n\nfunction eventToWire(event: TelemetryEvent): Record<string, unknown> {\n return {\n id: event.id,\n type: event.type,\n timestamp: event.timestamp,\n content_url: event.contentUrl,\n product_id: event.productId,\n turn: event.turn != null ? turnToWire(event.turn) : undefined,\n data: event.data ?? {},\n };\n}\n\nfunction initiatorToWire(i: Initiator): Record<string, unknown> {\n return {\n agent_id: i.agentId,\n manifest_ref: i.manifestRef,\n operator_id: i.operatorId,\n };\n}\n\nfunction userContextToWire(uc: UserContext): Record<string, unknown> {\n return {\n external_id: uc.externalId,\n segments: uc.segments ?? [],\n attributes: uc.attributes ?? {},\n };\n}\n\nfunction outcomeToWire(o: SessionOutcome): Record<string, unknown> {\n return {\n type: o.type,\n value_amount: o.valueAmount ?? 0,\n currency: o.currency ?? \"USD\",\n products: o.products ?? [],\n metadata: o.metadata ?? {},\n };\n}\n\n// ---------------------------------------------------------------------------\n// TelemetryClient\n// ---------------------------------------------------------------------------\n\n/**\n * Async client for recording OpenAttribution telemetry.\n *\n * Works in Node.js ≥ 18, Deno, browsers, and Edge runtimes (Vercel, Cloudflare).\n *\n * @example\n * ```ts\n * const client = new TelemetryClient({\n * endpoint: \"https://telemetry.example.com\",\n * apiKey: \"your-api-key\",\n * failSilently: true,\n * });\n *\n * const sessionId = await client.startSession({ contentScope: \"my-mix\" });\n *\n * await client.recordEvents(sessionId, [\n * { id: crypto.randomUUID(), type: \"content_retrieved\",\n * timestamp: new Date().toISOString(), contentUrl: \"https://...\" }\n * ]);\n *\n * await client.endSession(sessionId, { type: \"browse\" });\n * ```\n */\nexport class TelemetryClient {\n private readonly endpoint: string;\n private readonly apiKey: string | undefined;\n private readonly failSilently: boolean;\n private readonly timeout: number;\n private readonly maxRetries: number;\n\n constructor(options: TelemetryClientOptions) {\n this.endpoint = options.endpoint.replace(/\\/$/, \"\");\n this.apiKey = options.apiKey;\n this.failSilently = options.failSilently ?? true;\n this.timeout = options.timeout ?? 30_000;\n this.maxRetries = options.maxRetries ?? 3;\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (this.apiKey != null) h[\"X-API-Key\"] = this.apiKey;\n return h;\n }\n\n private async post(path: string, body: unknown): Promise<unknown> {\n const url = `${this.endpoint}${path}`;\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: this.headers(),\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (TRANSIENT_STATUS_CODES.has(res.status) && attempt < this.maxRetries) {\n const wait = 2 ** attempt * 1000 + Math.random() * 500;\n await sleep(wait);\n continue;\n }\n\n if (!res.ok) {\n throw new Error(`HTTP ${res.status} ${res.statusText} from ${url}`);\n }\n\n return await res.json();\n } catch (err) {\n lastError = err;\n if (attempt < this.maxRetries && isTransientError(err)) {\n const wait = 2 ** attempt * 1000 + Math.random() * 500;\n await sleep(wait);\n } else {\n break;\n }\n } finally {\n clearTimeout(timer);\n }\n }\n\n if (this.failSilently) {\n return null;\n }\n throw lastError;\n }\n\n /**\n * Start a new telemetry session.\n *\n * @returns Session ID string, or null on silent failure.\n */\n async startSession(options: StartSessionOptions = {}): Promise<string | null> {\n const result = await this.post(\"/session/start\", {\n initiator_type: options.initiatorType ?? \"user\",\n initiator:\n options.initiator != null ? initiatorToWire(options.initiator) : null,\n content_scope: options.contentScope,\n agent_id: options.agentId,\n external_session_id: options.externalSessionId,\n user_context:\n options.userContext != null\n ? userContextToWire(options.userContext)\n : {},\n manifest_ref: options.manifestRef,\n prior_session_ids: options.priorSessionIds ?? [],\n }) as { session_id?: string } | null;\n\n return result?.session_id ?? null;\n }\n\n /**\n * Record a single telemetry event.\n */\n async recordEvent(\n sessionId: string | null,\n eventType: EventType,\n options: {\n contentUrl?: string;\n productId?: string;\n turn?: ConversationTurn;\n data?: Record<string, unknown>;\n } = {},\n ): Promise<void> {\n if (sessionId == null) return;\n await this.recordEvents(sessionId, [\n {\n id: crypto.randomUUID(),\n type: eventType,\n timestamp: new Date().toISOString(),\n ...options,\n },\n ]);\n }\n\n /**\n * Record a batch of telemetry events.\n */\n async recordEvents(\n sessionId: string | null,\n events: TelemetryEvent[],\n ): Promise<void> {\n if (sessionId == null || events.length === 0) return;\n await this.post(\"/events\", {\n session_id: sessionId,\n events: events.map(eventToWire),\n });\n }\n\n /**\n * End a session with an outcome.\n */\n async endSession(\n sessionId: string | null,\n outcome: SessionOutcome,\n ): Promise<void> {\n if (sessionId == null) return;\n await this.post(\"/session/end\", {\n session_id: sessionId,\n outcome: outcomeToWire(outcome),\n });\n }\n\n /**\n * Upload a complete session in one request (bulk path).\n *\n * Useful for post-hoc reporting or when you've built the session\n * locally and want to submit it in one shot.\n *\n * @returns Server-assigned session ID, or null on silent failure.\n */\n async uploadSession(session: TelemetrySession): Promise<string | null> {\n const result = await this.post(\"/session/bulk\", sessionToWire(session)) as\n | { session_id?: string }\n | null;\n return result?.session_id ?? null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isTransientError(err: unknown): boolean {\n if (err instanceof Error) {\n // AbortError (timeout), network errors\n return (\n err.name === \"AbortError\" ||\n err.name === \"TypeError\" ||\n err.message.includes(\"fetch\")\n );\n }\n return false;\n}\n\nfunction sessionToWire(session: TelemetrySession): Record<string, unknown> {\n return {\n schema_version: session.schemaVersion ?? \"0.4\",\n session_id: session.sessionId,\n initiator_type: session.initiatorType ?? \"user\",\n initiator:\n session.initiator != null ? initiatorToWire(session.initiator) : null,\n agent_id: session.agentId,\n content_scope: session.contentScope,\n manifest_ref: session.manifestRef,\n prior_session_ids: session.priorSessionIds ?? [],\n started_at: session.startedAt,\n ended_at: session.endedAt,\n user_context:\n session.userContext != null\n ? userContextToWire(session.userContext)\n : {},\n events: session.events.map(eventToWire),\n outcome:\n session.outcome != null ? outcomeToWire(session.outcome) : undefined,\n };\n}\n","/**\n * OpenAttribution Telemetry — MCP session tracker.\n *\n * Provides session continuity across stateless MCP tool calls.\n * The calling agent passes a stable `sessionId` string; this module\n * maps it to an OA session UUID and reuses it across tool calls within\n * the same server process.\n *\n * Usage in an MCP tool:\n *\n * ```ts\n * import { TelemetryClient, MCPSessionTracker } from \"@openattribution/telemetry\";\n *\n * const client = new TelemetryClient({ endpoint: \"...\", apiKey: \"...\" });\n * const tracker = new MCPSessionTracker(client, \"my-agent\");\n *\n * // In your MCP tool handler:\n * server.tool(\"search_products\", { query: z.string(), sessionId: z.string().optional() },\n * async ({ query, sessionId }) => {\n * const results = await searchProducts(query);\n * await tracker.trackRetrieved(sessionId, results.map(r => r.url));\n * return formatResults(results);\n * }\n * );\n * ```\n */\n\nimport type { TelemetryEvent } from \"./types.js\";\nimport type { TelemetryClient } from \"./client.js\";\n\n/**\n * Session tracker for MCP agents.\n *\n * Maintains an in-process mapping of external session IDs to OA session UUIDs.\n * Sessions are created on first use and reused across subsequent tool calls.\n *\n * The in-process registry is intentionally simple — it works correctly for\n * single-process MCP servers. For distributed deployments, pass a shared\n * external store to the constructor.\n */\nexport class MCPSessionTracker {\n private readonly client: TelemetryClient;\n private readonly contentScope: string;\n private readonly registry = new Map<string, string | null>();\n\n /**\n * @param client - Configured TelemetryClient instance.\n * @param contentScope - Stable identifier for this agent's content scope\n * (e.g. mix ID, manifest reference, or descriptive slug like \"my-shopping-agent\").\n */\n constructor(client: TelemetryClient, contentScope = \"mcp-agent\") {\n this.client = client;\n this.contentScope = contentScope;\n }\n\n /**\n * Get or create an OA session for the given external session ID.\n *\n * If `externalSessionId` is undefined, a new anonymous session is created\n * on every call (no continuity across tool calls).\n *\n * @returns OA session ID string, or null on silent failure.\n */\n async getOrCreateSession(\n externalSessionId: string | undefined,\n ): Promise<string | null> {\n if (externalSessionId != null && this.registry.has(externalSessionId)) {\n return this.registry.get(externalSessionId) ?? null;\n }\n\n const sessionId = await this.client.startSession({\n contentScope: this.contentScope,\n ...(externalSessionId != null && { externalSessionId }),\n });\n\n if (externalSessionId != null) {\n this.registry.set(externalSessionId, sessionId);\n }\n\n return sessionId;\n }\n\n /**\n * Emit `content_retrieved` events for a list of URLs.\n *\n * Call this after fetching products, search results, or any content\n * that influenced the agent's response.\n *\n * @param externalSessionId - Caller-supplied conversation identifier.\n * @param urls - URLs of content retrieved during this tool call.\n */\n async trackRetrieved(\n externalSessionId: string | undefined,\n urls: string[],\n ): Promise<void> {\n if (urls.length === 0) return;\n const sessionId = await this.getOrCreateSession(externalSessionId);\n if (sessionId == null) return;\n\n const now = new Date().toISOString();\n const events: TelemetryEvent[] = urls.map((url) => ({\n id: crypto.randomUUID(),\n type: \"content_retrieved\" as const,\n timestamp: now,\n contentUrl: url,\n }));\n\n await this.client.recordEvents(sessionId, events);\n }\n\n /**\n * Emit `content_cited` events for content explicitly referenced in a response.\n *\n * Call this when you know which content the agent cited — e.g. the top\n * search result, an editorial quote, or a product recommendation.\n *\n * @param externalSessionId - Caller-supplied conversation identifier.\n * @param urls - URLs of content cited in the agent's response.\n * @param options - Optional citation metadata.\n */\n async trackCited(\n externalSessionId: string | undefined,\n urls: string[],\n options: {\n citationType?: \"direct_quote\" | \"paraphrase\" | \"reference\" | \"contradiction\";\n position?: \"primary\" | \"supporting\" | \"mentioned\";\n } = {},\n ): Promise<void> {\n if (urls.length === 0) return;\n const sessionId = await this.getOrCreateSession(externalSessionId);\n if (sessionId == null) return;\n\n const now = new Date().toISOString();\n const events: TelemetryEvent[] = urls.map((url) => ({\n id: crypto.randomUUID(),\n type: \"content_cited\" as const,\n timestamp: now,\n contentUrl: url,\n data: {\n ...(options.citationType != null && { citation_type: options.citationType }),\n ...(options.position != null && { position: options.position }),\n },\n }));\n\n await this.client.recordEvents(sessionId, events);\n }\n\n /**\n * End a session with an outcome.\n *\n * Call this when the conversation concludes — at checkout, after\n * the user clicks a link, or when the session times out.\n */\n async endSession(\n externalSessionId: string | undefined,\n outcome: { type: \"conversion\" | \"abandonment\" | \"browse\"; valueAmount?: number; currency?: string },\n ): Promise<void> {\n const sessionId = externalSessionId != null\n ? (this.registry.get(externalSessionId) ?? null)\n : null;\n\n if (sessionId == null) return;\n\n await this.client.endSession(sessionId, {\n type: outcome.type,\n ...(outcome.valueAmount != null && { valueAmount: outcome.valueAmount }),\n ...(outcome.currency != null && { currency: outcome.currency }),\n });\n\n this.registry.delete(externalSessionId!);\n }\n\n /** Number of active sessions in the registry. */\n get sessionCount(): number {\n return this.registry.size;\n }\n}\n","/**\n * OpenAttribution Telemetry — content URL extraction utilities.\n *\n * Helpers for extracting content URLs from AI-generated text, so they\n * can be recorded as telemetry events without manual instrumentation.\n */\n\n/** Matches `[text](url)` — standard Markdown links. */\nconst MARKDOWN_LINK_RE = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g;\n\n/** Matches bare URLs starting with http/https. */\nconst BARE_URL_RE = /https?:\\/\\/[^\\s<>\"')\\]]+/g;\n\n/**\n * Extract all HTTP/HTTPS URLs from Markdown-formatted text.\n *\n * Finds URLs in two forms:\n * - Markdown links: `[anchor text](https://...)`\n * - Bare URLs: `https://...`\n *\n * Results are deduplicated. Useful for extracting citation URLs from\n * AI model responses that include web search results.\n *\n * @example\n * ```ts\n * const text = \"According to [Wirecutter](https://nytimes.com/wirecutter/reviews/...), ...\";\n * const urls = extractCitationUrls(text);\n * // [\"https://nytimes.com/wirecutter/reviews/...\"]\n * ```\n */\nexport function extractCitationUrls(text: string): string[] {\n const urls = new Set<string>();\n\n for (const match of text.matchAll(MARKDOWN_LINK_RE)) {\n const url = match[2];\n if (url != null) urls.add(cleanUrl(url));\n }\n\n // Only look for bare URLs if Markdown links didn't cover everything\n for (const match of text.matchAll(BARE_URL_RE)) {\n urls.add(cleanUrl(match[0]));\n }\n\n return [...urls];\n}\n\n/**\n * Extract URLs from a list of search result objects.\n *\n * Accepts any object with a `url` string property — works with\n * Channel3, Exa, Tavily, and most search API responses.\n *\n * @example\n * ```ts\n * const products = await channel3.search({ query: \"headphones\" });\n * const urls = extractResultUrls(products);\n * await tracker.trackRetrieved(sessionId, urls);\n * ```\n */\nexport function extractResultUrls(\n results: Array<{ url?: string | null; link?: string | null }>,\n): string[] {\n const urls: string[] = [];\n for (const r of results) {\n const url = r.url ?? r.link;\n if (url != null && url.startsWith(\"http\")) {\n urls.push(url);\n }\n }\n return [...new Set(urls)];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Remove trailing punctuation that commonly attaches to bare URLs. */\nfunction cleanUrl(url: string): string {\n return url.replace(/[.,;:!?]+$/, \"\");\n}\n","/**\n * OpenAttribution Telemetry — ACP (Agentic Commerce Protocol) bridge.\n *\n * Converts a TelemetrySession into the `content_attribution` object\n * defined in the ACP RFC. Include this in a CheckoutSessionCreateRequest\n * or CheckoutSessionCompleteRequest.\n *\n * Reference: acp/rfc.content_attribution.md\n */\n\nimport type { TelemetrySession } from \"./types.js\";\n\nexport interface ContentAttributionRetrieved {\n content_url: string;\n timestamp: string;\n}\n\nexport interface ContentAttributionCited {\n content_url: string;\n timestamp: string;\n citation_type?: string;\n excerpt_tokens?: number;\n position?: string;\n content_hash?: string;\n}\n\nexport interface ConversationSummary {\n turn_count: number;\n topics: string[];\n}\n\n/**\n * ACP `content_attribution` object.\n * Include in CheckoutSessionCreateRequest.content_attribution.\n */\nexport interface ContentAttribution {\n content_scope?: string;\n content_retrieved: ContentAttributionRetrieved[];\n content_cited?: ContentAttributionCited[];\n conversation_summary?: ConversationSummary;\n}\n\n/**\n * Convert a TelemetrySession into an ACP `content_attribution` object.\n *\n * @example\n * ```ts\n * import { sessionToContentAttribution } from \"@openattribution/telemetry/acp\";\n *\n * const attribution = sessionToContentAttribution(session);\n *\n * // Include in ACP checkout request:\n * await acp.createCheckout({\n * cart: { ... },\n * content_attribution: attribution,\n * });\n * ```\n */\nexport function sessionToContentAttribution(\n session: TelemetrySession,\n): ContentAttribution {\n const retrieved: ContentAttributionRetrieved[] = session.events\n .filter((e) => e.type === \"content_retrieved\" && e.contentUrl != null)\n .map((e) => ({\n content_url: e.contentUrl!,\n timestamp: e.timestamp,\n }));\n\n const cited: ContentAttributionCited[] = session.events\n .filter((e) => e.type === \"content_cited\" && e.contentUrl != null)\n .map((e) => ({\n content_url: e.contentUrl!,\n timestamp: e.timestamp,\n ...(e.data?.[\"citation_type\"] != null && {\n citation_type: String(e.data[\"citation_type\"]),\n }),\n ...(e.data?.[\"excerpt_tokens\"] != null && {\n excerpt_tokens: Number(e.data[\"excerpt_tokens\"]),\n }),\n ...(e.data?.[\"position\"] != null && {\n position: String(e.data[\"position\"]),\n }),\n ...(e.data?.[\"content_hash\"] != null && {\n content_hash: String(e.data[\"content_hash\"]),\n }),\n }));\n\n const turnEvents = session.events.filter(\n (e) => e.type === \"turn_completed\" || e.type === \"turn_started\",\n );\n const turnCount = Math.max(\n session.events.filter((e) => e.type === \"turn_completed\").length,\n 1,\n );\n\n const topics = [\n ...new Set(\n session.events.flatMap((e) => e.turn?.topics ?? []),\n ),\n ];\n\n const result: ContentAttribution = {\n ...(session.contentScope != null && { content_scope: session.contentScope }),\n content_retrieved: retrieved,\n ...(cited.length > 0 && { content_cited: cited }),\n ...((turnEvents.length > 0 || topics.length > 0) && {\n conversation_summary: { turn_count: turnCount, topics },\n }),\n };\n\n return result;\n}\n","/**\n * OpenAttribution Telemetry — UCP (Universal Checkout Protocol) bridge.\n *\n * Converts a TelemetrySession into the UCP attribution extension object.\n *\n * Reference: ucp/EXTENSION.md\n */\n\nimport type { TelemetrySession } from \"./types.js\";\n\nexport interface UCPAttribution {\n content_scope?: string;\n prior_session_ids?: string[];\n content_retrieved: Array<{ content_url: string; timestamp: string }>;\n content_cited?: Array<{\n content_url: string;\n timestamp: string;\n citation_type?: string;\n position?: string;\n }>;\n conversation_summary?: {\n turn_count: number;\n topics: string[];\n };\n}\n\n/**\n * Convert a TelemetrySession into a UCP attribution extension object.\n *\n * @example\n * ```ts\n * import { sessionToAttribution } from \"@openattribution/telemetry/ucp\";\n *\n * const attribution = sessionToAttribution(session);\n *\n * // Include in UCP checkout:\n * await ucp.completeCheckout({\n * order: { ... },\n * extensions: { \"org.openattribution.telemetry\": attribution },\n * });\n * ```\n */\nexport function sessionToAttribution(session: TelemetrySession): UCPAttribution {\n const retrieved = session.events\n .filter((e) => e.type === \"content_retrieved\" && e.contentUrl != null)\n .map((e) => ({ content_url: e.contentUrl!, timestamp: e.timestamp }));\n\n const cited = session.events\n .filter((e) => e.type === \"content_cited\" && e.contentUrl != null)\n .map((e) => ({\n content_url: e.contentUrl!,\n timestamp: e.timestamp,\n ...(e.data?.[\"citation_type\"] != null && {\n citation_type: String(e.data[\"citation_type\"]),\n }),\n ...(e.data?.[\"position\"] != null && {\n position: String(e.data[\"position\"]),\n }),\n }));\n\n const turnCount = Math.max(\n session.events.filter((e) => e.type === \"turn_completed\").length,\n 1,\n );\n const topics = [...new Set(session.events.flatMap((e) => e.turn?.topics ?? []))];\n const priorIds = (session.priorSessionIds ?? []).filter(Boolean);\n\n return {\n ...(session.contentScope != null && { content_scope: session.contentScope }),\n ...(priorIds.length > 0 && { prior_session_ids: priorIds }),\n content_retrieved: retrieved,\n ...(cited.length > 0 && { content_cited: cited }),\n ...((topics.length > 0 || turnCount > 0) && {\n conversation_summary: { turn_count: turnCount, topics },\n }),\n };\n}\n"],"mappings":"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","names":[]}
|