@firstflow/core 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1865 @@
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
+ DEFAULT_FIRSTFLOW_BASE_URL: () => DEFAULT_FIRSTFLOW_BASE_URL,
24
+ FIRSTFLOW_SERVER_PACKAGE_VERSION: () => FIRSTFLOW_SERVER_PACKAGE_VERSION,
25
+ feedback: () => feedback,
26
+ identify: () => identify,
27
+ listAccessibleAgents: () => listAccessibleAgents,
28
+ observe: () => observe,
29
+ outcome: () => outcome,
30
+ trace: () => trace,
31
+ track: () => track,
32
+ wrapClient: () => wrapClient
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // node_modules/tsup/assets/cjs_shims.js
37
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
38
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
39
+
40
+ // src/runtime.ts
41
+ var import_sdk_node = require("@opentelemetry/sdk-node");
42
+ var import_resources = require("@opentelemetry/resources");
43
+ var import_instrumentation_openai = require("@opentelemetry/instrumentation-openai");
44
+ var import_instrumentation_anthropic = require("@traceloop/instrumentation-anthropic");
45
+
46
+ // node_modules/@opentelemetry/api/build/esm/version.js
47
+ var VERSION = "1.9.1";
48
+
49
+ // node_modules/@opentelemetry/api/build/esm/internal/semver.js
50
+ var re = /^(\d+)\.(\d+)\.(\d+)(-(.+))?$/;
51
+ function _makeCompatibilityCheck(ownVersion) {
52
+ const acceptedVersions = /* @__PURE__ */ new Set([ownVersion]);
53
+ const rejectedVersions = /* @__PURE__ */ new Set();
54
+ const myVersionMatch = ownVersion.match(re);
55
+ if (!myVersionMatch) {
56
+ return () => false;
57
+ }
58
+ const ownVersionParsed = {
59
+ major: +myVersionMatch[1],
60
+ minor: +myVersionMatch[2],
61
+ patch: +myVersionMatch[3],
62
+ prerelease: myVersionMatch[4]
63
+ };
64
+ if (ownVersionParsed.prerelease != null) {
65
+ return function isExactmatch(globalVersion) {
66
+ return globalVersion === ownVersion;
67
+ };
68
+ }
69
+ function _reject(v) {
70
+ rejectedVersions.add(v);
71
+ return false;
72
+ }
73
+ function _accept(v) {
74
+ acceptedVersions.add(v);
75
+ return true;
76
+ }
77
+ return function isCompatible2(globalVersion) {
78
+ if (acceptedVersions.has(globalVersion)) {
79
+ return true;
80
+ }
81
+ if (rejectedVersions.has(globalVersion)) {
82
+ return false;
83
+ }
84
+ const globalVersionMatch = globalVersion.match(re);
85
+ if (!globalVersionMatch) {
86
+ return _reject(globalVersion);
87
+ }
88
+ const globalVersionParsed = {
89
+ major: +globalVersionMatch[1],
90
+ minor: +globalVersionMatch[2],
91
+ patch: +globalVersionMatch[3],
92
+ prerelease: globalVersionMatch[4]
93
+ };
94
+ if (globalVersionParsed.prerelease != null) {
95
+ return _reject(globalVersion);
96
+ }
97
+ if (ownVersionParsed.major !== globalVersionParsed.major) {
98
+ return _reject(globalVersion);
99
+ }
100
+ if (ownVersionParsed.major === 0) {
101
+ if (ownVersionParsed.minor === globalVersionParsed.minor && ownVersionParsed.patch <= globalVersionParsed.patch) {
102
+ return _accept(globalVersion);
103
+ }
104
+ return _reject(globalVersion);
105
+ }
106
+ if (ownVersionParsed.minor <= globalVersionParsed.minor) {
107
+ return _accept(globalVersion);
108
+ }
109
+ return _reject(globalVersion);
110
+ };
111
+ }
112
+ var isCompatible = _makeCompatibilityCheck(VERSION);
113
+
114
+ // node_modules/@opentelemetry/api/build/esm/internal/global-utils.js
115
+ var major = VERSION.split(".")[0];
116
+ var GLOBAL_OPENTELEMETRY_API_KEY = /* @__PURE__ */ Symbol.for(`opentelemetry.js.api.${major}`);
117
+ var _global = typeof globalThis === "object" ? globalThis : typeof self === "object" ? self : typeof window === "object" ? window : typeof global === "object" ? global : {};
118
+ function registerGlobal(type, instance, diag2, allowOverride = false) {
119
+ var _a;
120
+ const api = _global[GLOBAL_OPENTELEMETRY_API_KEY] = (_a = _global[GLOBAL_OPENTELEMETRY_API_KEY]) !== null && _a !== void 0 ? _a : {
121
+ version: VERSION
122
+ };
123
+ if (!allowOverride && api[type]) {
124
+ const err = new Error(`@opentelemetry/api: Attempted duplicate registration of API: ${type}`);
125
+ diag2.error(err.stack || err.message);
126
+ return false;
127
+ }
128
+ if (api.version !== VERSION) {
129
+ const err = new Error(`@opentelemetry/api: Registration of version v${api.version} for ${type} does not match previously registered API v${VERSION}`);
130
+ diag2.error(err.stack || err.message);
131
+ return false;
132
+ }
133
+ api[type] = instance;
134
+ diag2.debug(`@opentelemetry/api: Registered a global for ${type} v${VERSION}.`);
135
+ return true;
136
+ }
137
+ function getGlobal(type) {
138
+ var _a, _b;
139
+ const globalVersion = (_a = _global[GLOBAL_OPENTELEMETRY_API_KEY]) === null || _a === void 0 ? void 0 : _a.version;
140
+ if (!globalVersion || !isCompatible(globalVersion)) {
141
+ return;
142
+ }
143
+ return (_b = _global[GLOBAL_OPENTELEMETRY_API_KEY]) === null || _b === void 0 ? void 0 : _b[type];
144
+ }
145
+ function unregisterGlobal(type, diag2) {
146
+ diag2.debug(`@opentelemetry/api: Unregistering a global for ${type} v${VERSION}.`);
147
+ const api = _global[GLOBAL_OPENTELEMETRY_API_KEY];
148
+ if (api) {
149
+ delete api[type];
150
+ }
151
+ }
152
+
153
+ // node_modules/@opentelemetry/api/build/esm/diag/ComponentLogger.js
154
+ var DiagComponentLogger = class {
155
+ constructor(props) {
156
+ this._namespace = props.namespace || "DiagComponentLogger";
157
+ }
158
+ debug(...args) {
159
+ return logProxy("debug", this._namespace, args);
160
+ }
161
+ error(...args) {
162
+ return logProxy("error", this._namespace, args);
163
+ }
164
+ info(...args) {
165
+ return logProxy("info", this._namespace, args);
166
+ }
167
+ warn(...args) {
168
+ return logProxy("warn", this._namespace, args);
169
+ }
170
+ verbose(...args) {
171
+ return logProxy("verbose", this._namespace, args);
172
+ }
173
+ };
174
+ function logProxy(funcName, namespace, args) {
175
+ const logger = getGlobal("diag");
176
+ if (!logger) {
177
+ return;
178
+ }
179
+ return logger[funcName](namespace, ...args);
180
+ }
181
+
182
+ // node_modules/@opentelemetry/api/build/esm/diag/types.js
183
+ var DiagLogLevel;
184
+ (function(DiagLogLevel2) {
185
+ DiagLogLevel2[DiagLogLevel2["NONE"] = 0] = "NONE";
186
+ DiagLogLevel2[DiagLogLevel2["ERROR"] = 30] = "ERROR";
187
+ DiagLogLevel2[DiagLogLevel2["WARN"] = 50] = "WARN";
188
+ DiagLogLevel2[DiagLogLevel2["INFO"] = 60] = "INFO";
189
+ DiagLogLevel2[DiagLogLevel2["DEBUG"] = 70] = "DEBUG";
190
+ DiagLogLevel2[DiagLogLevel2["VERBOSE"] = 80] = "VERBOSE";
191
+ DiagLogLevel2[DiagLogLevel2["ALL"] = 9999] = "ALL";
192
+ })(DiagLogLevel || (DiagLogLevel = {}));
193
+
194
+ // node_modules/@opentelemetry/api/build/esm/diag/internal/logLevelLogger.js
195
+ function createLogLevelDiagLogger(maxLevel, logger) {
196
+ if (maxLevel < DiagLogLevel.NONE) {
197
+ maxLevel = DiagLogLevel.NONE;
198
+ } else if (maxLevel > DiagLogLevel.ALL) {
199
+ maxLevel = DiagLogLevel.ALL;
200
+ }
201
+ logger = logger || {};
202
+ function _filterFunc(funcName, theLevel) {
203
+ const theFunc = logger[funcName];
204
+ if (typeof theFunc === "function" && maxLevel >= theLevel) {
205
+ return theFunc.bind(logger);
206
+ }
207
+ return function() {
208
+ };
209
+ }
210
+ return {
211
+ error: _filterFunc("error", DiagLogLevel.ERROR),
212
+ warn: _filterFunc("warn", DiagLogLevel.WARN),
213
+ info: _filterFunc("info", DiagLogLevel.INFO),
214
+ debug: _filterFunc("debug", DiagLogLevel.DEBUG),
215
+ verbose: _filterFunc("verbose", DiagLogLevel.VERBOSE)
216
+ };
217
+ }
218
+
219
+ // node_modules/@opentelemetry/api/build/esm/api/diag.js
220
+ var API_NAME = "diag";
221
+ var DiagAPI = class _DiagAPI {
222
+ /** Get the singleton instance of the DiagAPI API */
223
+ static instance() {
224
+ if (!this._instance) {
225
+ this._instance = new _DiagAPI();
226
+ }
227
+ return this._instance;
228
+ }
229
+ /**
230
+ * Private internal constructor
231
+ * @private
232
+ */
233
+ constructor() {
234
+ function _logProxy(funcName) {
235
+ return function(...args) {
236
+ const logger = getGlobal("diag");
237
+ if (!logger)
238
+ return;
239
+ return logger[funcName](...args);
240
+ };
241
+ }
242
+ const self2 = this;
243
+ const setLogger = (logger, optionsOrLogLevel = { logLevel: DiagLogLevel.INFO }) => {
244
+ var _a, _b, _c;
245
+ if (logger === self2) {
246
+ const err = new Error("Cannot use diag as the logger for itself. Please use a DiagLogger implementation like ConsoleDiagLogger or a custom implementation");
247
+ self2.error((_a = err.stack) !== null && _a !== void 0 ? _a : err.message);
248
+ return false;
249
+ }
250
+ if (typeof optionsOrLogLevel === "number") {
251
+ optionsOrLogLevel = {
252
+ logLevel: optionsOrLogLevel
253
+ };
254
+ }
255
+ const oldLogger = getGlobal("diag");
256
+ const newLogger = createLogLevelDiagLogger((_b = optionsOrLogLevel.logLevel) !== null && _b !== void 0 ? _b : DiagLogLevel.INFO, logger);
257
+ if (oldLogger && !optionsOrLogLevel.suppressOverrideMessage) {
258
+ const stack = (_c = new Error().stack) !== null && _c !== void 0 ? _c : "<failed to generate stacktrace>";
259
+ oldLogger.warn(`Current logger will be overwritten from ${stack}`);
260
+ newLogger.warn(`Current logger will overwrite one already registered from ${stack}`);
261
+ }
262
+ return registerGlobal("diag", newLogger, self2, true);
263
+ };
264
+ self2.setLogger = setLogger;
265
+ self2.disable = () => {
266
+ unregisterGlobal(API_NAME, self2);
267
+ };
268
+ self2.createComponentLogger = (options) => {
269
+ return new DiagComponentLogger(options);
270
+ };
271
+ self2.verbose = _logProxy("verbose");
272
+ self2.debug = _logProxy("debug");
273
+ self2.info = _logProxy("info");
274
+ self2.warn = _logProxy("warn");
275
+ self2.error = _logProxy("error");
276
+ }
277
+ };
278
+
279
+ // node_modules/@opentelemetry/api/build/esm/baggage/internal/baggage-impl.js
280
+ var BaggageImpl = class _BaggageImpl {
281
+ constructor(entries) {
282
+ this._entries = entries ? new Map(entries) : /* @__PURE__ */ new Map();
283
+ }
284
+ getEntry(key) {
285
+ const entry = this._entries.get(key);
286
+ if (!entry) {
287
+ return void 0;
288
+ }
289
+ return Object.assign({}, entry);
290
+ }
291
+ getAllEntries() {
292
+ return Array.from(this._entries.entries());
293
+ }
294
+ setEntry(key, entry) {
295
+ const newBaggage = new _BaggageImpl(this._entries);
296
+ newBaggage._entries.set(key, entry);
297
+ return newBaggage;
298
+ }
299
+ removeEntry(key) {
300
+ const newBaggage = new _BaggageImpl(this._entries);
301
+ newBaggage._entries.delete(key);
302
+ return newBaggage;
303
+ }
304
+ removeEntries(...keys) {
305
+ const newBaggage = new _BaggageImpl(this._entries);
306
+ for (const key of keys) {
307
+ newBaggage._entries.delete(key);
308
+ }
309
+ return newBaggage;
310
+ }
311
+ clear() {
312
+ return new _BaggageImpl();
313
+ }
314
+ };
315
+
316
+ // node_modules/@opentelemetry/api/build/esm/baggage/utils.js
317
+ var diag = DiagAPI.instance();
318
+ function createBaggage(entries = {}) {
319
+ return new BaggageImpl(new Map(Object.entries(entries)));
320
+ }
321
+
322
+ // node_modules/@opentelemetry/api/build/esm/context/context.js
323
+ function createContextKey(description) {
324
+ return Symbol.for(description);
325
+ }
326
+ var BaseContext = class _BaseContext {
327
+ /**
328
+ * Construct a new context which inherits values from an optional parent context.
329
+ *
330
+ * @param parentContext a context from which to inherit values
331
+ */
332
+ constructor(parentContext) {
333
+ const self2 = this;
334
+ self2._currentContext = parentContext ? new Map(parentContext) : /* @__PURE__ */ new Map();
335
+ self2.getValue = (key) => self2._currentContext.get(key);
336
+ self2.setValue = (key, value) => {
337
+ const context2 = new _BaseContext(self2._currentContext);
338
+ context2._currentContext.set(key, value);
339
+ return context2;
340
+ };
341
+ self2.deleteValue = (key) => {
342
+ const context2 = new _BaseContext(self2._currentContext);
343
+ context2._currentContext.delete(key);
344
+ return context2;
345
+ };
346
+ }
347
+ };
348
+ var ROOT_CONTEXT = new BaseContext();
349
+
350
+ // node_modules/@opentelemetry/api/build/esm/propagation/TextMapPropagator.js
351
+ var defaultTextMapGetter = {
352
+ get(carrier, key) {
353
+ if (carrier == null) {
354
+ return void 0;
355
+ }
356
+ return carrier[key];
357
+ },
358
+ keys(carrier) {
359
+ if (carrier == null) {
360
+ return [];
361
+ }
362
+ return Object.keys(carrier);
363
+ }
364
+ };
365
+ var defaultTextMapSetter = {
366
+ set(carrier, key, value) {
367
+ if (carrier == null) {
368
+ return;
369
+ }
370
+ carrier[key] = value;
371
+ }
372
+ };
373
+
374
+ // node_modules/@opentelemetry/api/build/esm/context/NoopContextManager.js
375
+ var NoopContextManager = class {
376
+ active() {
377
+ return ROOT_CONTEXT;
378
+ }
379
+ with(_context, fn, thisArg, ...args) {
380
+ return fn.call(thisArg, ...args);
381
+ }
382
+ bind(_context, target) {
383
+ return target;
384
+ }
385
+ enable() {
386
+ return this;
387
+ }
388
+ disable() {
389
+ return this;
390
+ }
391
+ };
392
+
393
+ // node_modules/@opentelemetry/api/build/esm/api/context.js
394
+ var API_NAME2 = "context";
395
+ var NOOP_CONTEXT_MANAGER = new NoopContextManager();
396
+ var ContextAPI = class _ContextAPI {
397
+ /** Empty private constructor prevents end users from constructing a new instance of the API */
398
+ constructor() {
399
+ }
400
+ /** Get the singleton instance of the Context API */
401
+ static getInstance() {
402
+ if (!this._instance) {
403
+ this._instance = new _ContextAPI();
404
+ }
405
+ return this._instance;
406
+ }
407
+ /**
408
+ * Set the current context manager.
409
+ *
410
+ * @returns true if the context manager was successfully registered, else false
411
+ */
412
+ setGlobalContextManager(contextManager) {
413
+ return registerGlobal(API_NAME2, contextManager, DiagAPI.instance());
414
+ }
415
+ /**
416
+ * Get the currently active context
417
+ */
418
+ active() {
419
+ return this._getContextManager().active();
420
+ }
421
+ /**
422
+ * Execute a function with an active context
423
+ *
424
+ * @param context context to be active during function execution
425
+ * @param fn function to execute in a context
426
+ * @param thisArg optional receiver to be used for calling fn
427
+ * @param args optional arguments forwarded to fn
428
+ */
429
+ with(context2, fn, thisArg, ...args) {
430
+ return this._getContextManager().with(context2, fn, thisArg, ...args);
431
+ }
432
+ /**
433
+ * Bind a context to a target function or event emitter
434
+ *
435
+ * @param context context to bind to the event emitter or function. Defaults to the currently active context
436
+ * @param target function or event emitter to bind
437
+ */
438
+ bind(context2, target) {
439
+ return this._getContextManager().bind(context2, target);
440
+ }
441
+ _getContextManager() {
442
+ return getGlobal(API_NAME2) || NOOP_CONTEXT_MANAGER;
443
+ }
444
+ /** Disable and remove the global context manager */
445
+ disable() {
446
+ this._getContextManager().disable();
447
+ unregisterGlobal(API_NAME2, DiagAPI.instance());
448
+ }
449
+ };
450
+
451
+ // node_modules/@opentelemetry/api/build/esm/context-api.js
452
+ var context = ContextAPI.getInstance();
453
+
454
+ // node_modules/@opentelemetry/api/build/esm/propagation/NoopTextMapPropagator.js
455
+ var NoopTextMapPropagator = class {
456
+ /** Noop inject function does nothing */
457
+ inject(_context, _carrier) {
458
+ }
459
+ /** Noop extract function does nothing and returns the input context */
460
+ extract(context2, _carrier) {
461
+ return context2;
462
+ }
463
+ fields() {
464
+ return [];
465
+ }
466
+ };
467
+
468
+ // node_modules/@opentelemetry/api/build/esm/baggage/context-helpers.js
469
+ var BAGGAGE_KEY = createContextKey("OpenTelemetry Baggage Key");
470
+ function getBaggage(context2) {
471
+ return context2.getValue(BAGGAGE_KEY) || void 0;
472
+ }
473
+ function getActiveBaggage() {
474
+ return getBaggage(ContextAPI.getInstance().active());
475
+ }
476
+ function setBaggage(context2, baggage) {
477
+ return context2.setValue(BAGGAGE_KEY, baggage);
478
+ }
479
+ function deleteBaggage(context2) {
480
+ return context2.deleteValue(BAGGAGE_KEY);
481
+ }
482
+
483
+ // node_modules/@opentelemetry/api/build/esm/api/propagation.js
484
+ var API_NAME3 = "propagation";
485
+ var NOOP_TEXT_MAP_PROPAGATOR = new NoopTextMapPropagator();
486
+ var PropagationAPI = class _PropagationAPI {
487
+ /** Empty private constructor prevents end users from constructing a new instance of the API */
488
+ constructor() {
489
+ this.createBaggage = createBaggage;
490
+ this.getBaggage = getBaggage;
491
+ this.getActiveBaggage = getActiveBaggage;
492
+ this.setBaggage = setBaggage;
493
+ this.deleteBaggage = deleteBaggage;
494
+ }
495
+ /** Get the singleton instance of the Propagator API */
496
+ static getInstance() {
497
+ if (!this._instance) {
498
+ this._instance = new _PropagationAPI();
499
+ }
500
+ return this._instance;
501
+ }
502
+ /**
503
+ * Set the current propagator.
504
+ *
505
+ * @returns true if the propagator was successfully registered, else false
506
+ */
507
+ setGlobalPropagator(propagator) {
508
+ return registerGlobal(API_NAME3, propagator, DiagAPI.instance());
509
+ }
510
+ /**
511
+ * Inject context into a carrier to be propagated inter-process
512
+ *
513
+ * @param context Context carrying tracing data to inject
514
+ * @param carrier carrier to inject context into
515
+ * @param setter Function used to set values on the carrier
516
+ */
517
+ inject(context2, carrier, setter = defaultTextMapSetter) {
518
+ return this._getGlobalPropagator().inject(context2, carrier, setter);
519
+ }
520
+ /**
521
+ * Extract context from a carrier
522
+ *
523
+ * @param context Context which the newly created context will inherit from
524
+ * @param carrier Carrier to extract context from
525
+ * @param getter Function used to extract keys from a carrier
526
+ */
527
+ extract(context2, carrier, getter = defaultTextMapGetter) {
528
+ return this._getGlobalPropagator().extract(context2, carrier, getter);
529
+ }
530
+ /**
531
+ * Return a list of all fields which may be used by the propagator.
532
+ */
533
+ fields() {
534
+ return this._getGlobalPropagator().fields();
535
+ }
536
+ /** Remove the global propagator */
537
+ disable() {
538
+ unregisterGlobal(API_NAME3, DiagAPI.instance());
539
+ }
540
+ _getGlobalPropagator() {
541
+ return getGlobal(API_NAME3) || NOOP_TEXT_MAP_PROPAGATOR;
542
+ }
543
+ };
544
+
545
+ // node_modules/@opentelemetry/api/build/esm/propagation-api.js
546
+ var propagation = PropagationAPI.getInstance();
547
+
548
+ // src/firstflow-span-processor.ts
549
+ var FLUSH_SIZE = 20;
550
+ var FLUSH_INTERVAL_MS = 2e3;
551
+ var REQUEST_TIMEOUT_MS = 1e4;
552
+ var MAX_QUEUE_SIZE = 500;
553
+ var AGENT_ID_ATTR = "firstflow.agent_id";
554
+ function spanToOtlpJson(span) {
555
+ const attrs = span.attributes;
556
+ const otlpAttrs = [];
557
+ for (const [key, val] of Object.entries(attrs)) {
558
+ if (val === void 0) continue;
559
+ if (key === AGENT_ID_ATTR) continue;
560
+ if (typeof val === "string") {
561
+ otlpAttrs.push({ key, value: { stringValue: val } });
562
+ } else if (typeof val === "number") {
563
+ if (Number.isInteger(val)) {
564
+ otlpAttrs.push({ key, value: { intValue: val } });
565
+ } else {
566
+ otlpAttrs.push({ key, value: { doubleValue: val } });
567
+ }
568
+ } else if (typeof val === "boolean") {
569
+ otlpAttrs.push({ key, value: { boolValue: val } });
570
+ }
571
+ }
572
+ const startNs = BigInt(span.startTime[0]) * BigInt(1e9) + BigInt(span.startTime[1]);
573
+ const endNs = BigInt(span.endTime[0]) * BigInt(1e9) + BigInt(span.endTime[1]);
574
+ const parentId = span.parentSpanId;
575
+ return {
576
+ traceId: span.spanContext().traceId,
577
+ spanId: span.spanContext().spanId,
578
+ parentSpanId: parentId || void 0,
579
+ name: span.name,
580
+ startTimeUnixNano: startNs.toString(),
581
+ endTimeUnixNano: endNs.toString(),
582
+ status: span.status ? { code: span.status.code, message: span.status.message } : void 0,
583
+ attributes: otlpAttrs
584
+ };
585
+ }
586
+ var FirstflowSpanProcessor = class {
587
+ constructor(opts) {
588
+ this.opts = opts;
589
+ }
590
+ opts;
591
+ /** Spans partitioned by agent id — partitioning happens at ingest, not flush. */
592
+ buffers = /* @__PURE__ */ new Map();
593
+ bufferedCount = 0;
594
+ flushTimer;
595
+ onStart(span, parentContext) {
596
+ const agentId = propagation.getBaggage(parentContext)?.getEntry(AGENT_ID_ATTR)?.value;
597
+ if (agentId) span.setAttribute(AGENT_ID_ATTR, agentId);
598
+ }
599
+ onEnd(span) {
600
+ const genAi = span.attributes["gen_ai.system"];
601
+ if (!genAi) return;
602
+ const agentId = span.attributes[AGENT_ID_ATTR];
603
+ if (typeof agentId !== "string" || !agentId) return;
604
+ let bucket = this.buffers.get(agentId);
605
+ if (!bucket) {
606
+ bucket = [];
607
+ this.buffers.set(agentId, bucket);
608
+ }
609
+ if (this.bufferedCount >= MAX_QUEUE_SIZE) {
610
+ const firstKey = this.buffers.keys().next().value;
611
+ if (firstKey !== void 0) {
612
+ const b = this.buffers.get(firstKey);
613
+ b.shift();
614
+ if (b.length === 0) this.buffers.delete(firstKey);
615
+ this.bufferedCount--;
616
+ }
617
+ }
618
+ bucket.push(span);
619
+ this.bufferedCount++;
620
+ if (this.bufferedCount >= FLUSH_SIZE) {
621
+ void this.flush();
622
+ } else {
623
+ this.scheduleFlush();
624
+ }
625
+ }
626
+ async shutdown() {
627
+ if (this.flushTimer != null) {
628
+ clearTimeout(this.flushTimer);
629
+ this.flushTimer = void 0;
630
+ }
631
+ await this.flush();
632
+ }
633
+ async forceFlush() {
634
+ await this.flush();
635
+ }
636
+ scheduleFlush() {
637
+ if (this.flushTimer != null) return;
638
+ this.flushTimer = setTimeout(() => {
639
+ this.flushTimer = void 0;
640
+ void this.flush();
641
+ }, FLUSH_INTERVAL_MS);
642
+ }
643
+ async flush() {
644
+ if (this.flushTimer != null) {
645
+ clearTimeout(this.flushTimer);
646
+ this.flushTimer = void 0;
647
+ }
648
+ if (this.bufferedCount === 0) return;
649
+ const batches = [...this.buffers.entries()];
650
+ this.buffers.clear();
651
+ this.bufferedCount = 0;
652
+ await Promise.all(batches.map(([agentId, spans]) => this.post(agentId, spans)));
653
+ }
654
+ async post(agentId, spans) {
655
+ if (spans.length === 0) return;
656
+ const otlpSpans = spans.map(spanToOtlpJson);
657
+ const body = JSON.stringify({
658
+ resourceSpans: [
659
+ {
660
+ resource: {
661
+ attributes: [
662
+ { key: "service.name", value: { stringValue: "firstflow-server" } }
663
+ ]
664
+ },
665
+ scopeSpans: [{ spans: otlpSpans }]
666
+ }
667
+ ]
668
+ });
669
+ const url = `${this.opts.baseUrl}/v1/agents/${encodeURIComponent(agentId)}/traces`;
670
+ const ac = new AbortController();
671
+ const timer = setTimeout(() => ac.abort(), REQUEST_TIMEOUT_MS);
672
+ try {
673
+ await fetch(url, {
674
+ method: "POST",
675
+ headers: {
676
+ Authorization: `Bearer ${this.opts.apiKey}`,
677
+ "Content-Type": "application/json"
678
+ },
679
+ body,
680
+ signal: ac.signal
681
+ });
682
+ } catch {
683
+ } finally {
684
+ clearTimeout(timer);
685
+ }
686
+ }
687
+ };
688
+
689
+ // src/sanitize.ts
690
+ var SENSITIVE_KEY_RE = /(password|secret|token|api[-_]?key|cookie|auth)/i;
691
+ var DEFAULT_MAX_DEPTH = 4;
692
+ var DEFAULT_MAX_STRING_LEN = 1024;
693
+ var RESERVED_KEYS = /* @__PURE__ */ new Set([
694
+ "token",
695
+ "api_key",
696
+ "distinct_id",
697
+ "$anon_distinct_id",
698
+ "$device_id",
699
+ "$session_id",
700
+ "$user_id",
701
+ "$set",
702
+ "$set_once",
703
+ "groups",
704
+ "$groups",
705
+ "event",
706
+ "timestamp",
707
+ "$insert_id",
708
+ "$lib",
709
+ "$lib_version",
710
+ "$current_url",
711
+ "$host",
712
+ "$pathname",
713
+ "$referrer",
714
+ "$referring_domain"
715
+ ]);
716
+ function isReservedKey(key) {
717
+ return RESERVED_KEYS.has(key) || key.startsWith("$");
718
+ }
719
+ function sanitizeValueInternal(value, depth, maxDepth, maxStringLen) {
720
+ if (value === null || value === void 0) return value;
721
+ if (typeof value === "string") {
722
+ if (value.length <= maxStringLen) return value;
723
+ return `${value.slice(0, maxStringLen)}\u2026truncated`;
724
+ }
725
+ if (typeof value === "number" || typeof value === "boolean") return value;
726
+ if (typeof value !== "object") return void 0;
727
+ if (depth >= maxDepth) return void 0;
728
+ if (Array.isArray(value)) {
729
+ const next = [];
730
+ for (const entry of value) {
731
+ next.push(sanitizeValueInternal(entry, depth + 1, maxDepth, maxStringLen));
732
+ }
733
+ return next;
734
+ }
735
+ const obj = value;
736
+ const out = {};
737
+ for (const [k, v] of Object.entries(obj)) {
738
+ const keyStr = String(k);
739
+ if (!isReservedKey(keyStr) && SENSITIVE_KEY_RE.test(keyStr)) continue;
740
+ const sv = sanitizeValueInternal(v, depth + 1, maxDepth, maxStringLen);
741
+ if (sv !== void 0) out[keyStr] = sv;
742
+ }
743
+ return Object.keys(out).length > 0 ? out : {};
744
+ }
745
+ function sanitizeAnalyticsProperties(properties, opts = {}) {
746
+ try {
747
+ const maxDepth = typeof opts.maxDepth === "number" && opts.maxDepth >= 1 ? opts.maxDepth : DEFAULT_MAX_DEPTH;
748
+ const maxStringLen = typeof opts.maxStringLen === "number" && opts.maxStringLen >= 1 ? opts.maxStringLen : DEFAULT_MAX_STRING_LEN;
749
+ const sanitized = sanitizeValueInternal(properties, 0, maxDepth, maxStringLen);
750
+ return sanitized && typeof sanitized === "object" && !Array.isArray(sanitized) ? sanitized : {};
751
+ } catch {
752
+ return {};
753
+ }
754
+ }
755
+
756
+ // src/analytics-client.ts
757
+ var FLUSH_SIZE2 = 20;
758
+ var FLUSH_INTERVAL_MS2 = 1e3;
759
+ var MAX_QUEUE_SIZE2 = 1e3;
760
+ var REQUEST_TIMEOUT_MS2 = 5e3;
761
+ var MAX_BACKOFF_MS = 8e3;
762
+ var FirstflowAnalyticsClient = class {
763
+ constructor(opts) {
764
+ this.opts = opts;
765
+ }
766
+ opts;
767
+ buffer = [];
768
+ flushTimer;
769
+ backoffMs = 1e3;
770
+ closed = false;
771
+ track(userId, event, properties) {
772
+ if (this.closed) return;
773
+ this.enqueue({
774
+ event,
775
+ message_id: crypto.randomUUID(),
776
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
777
+ session_id: "",
778
+ anonymous_id: "",
779
+ user_id: userId,
780
+ sdk_version: this.opts.sdkVersion,
781
+ ...properties && Object.keys(properties).length > 0 ? { props: sanitizeAnalyticsProperties(properties) } : {}
782
+ });
783
+ }
784
+ identify(userId, traits) {
785
+ if (this.closed) return;
786
+ this.enqueue({
787
+ event: "user_identified",
788
+ message_id: crypto.randomUUID(),
789
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
790
+ session_id: "",
791
+ anonymous_id: "",
792
+ user_id: userId,
793
+ sdk_version: this.opts.sdkVersion,
794
+ ...traits && Object.keys(traits).length > 0 ? { props: sanitizeAnalyticsProperties(traits) } : {}
795
+ });
796
+ }
797
+ async shutdown() {
798
+ if (this.closed) return;
799
+ this.closed = true;
800
+ if (this.flushTimer != null) {
801
+ clearTimeout(this.flushTimer);
802
+ this.flushTimer = void 0;
803
+ }
804
+ await this.flush();
805
+ }
806
+ enqueue(event) {
807
+ if (this.buffer.length >= MAX_QUEUE_SIZE2) {
808
+ this.buffer.shift();
809
+ }
810
+ this.buffer.push(event);
811
+ if (this.buffer.length >= FLUSH_SIZE2) {
812
+ void this.flush();
813
+ } else {
814
+ this.scheduleFlush();
815
+ }
816
+ }
817
+ scheduleFlush() {
818
+ if (this.flushTimer != null) return;
819
+ this.flushTimer = setTimeout(() => {
820
+ this.flushTimer = void 0;
821
+ void this.flush();
822
+ }, FLUSH_INTERVAL_MS2);
823
+ }
824
+ async flush() {
825
+ if (this.flushTimer != null) {
826
+ clearTimeout(this.flushTimer);
827
+ this.flushTimer = void 0;
828
+ }
829
+ if (this.buffer.length === 0) return;
830
+ const batch = this.buffer.splice(0, this.buffer.length);
831
+ const url = `${this.opts.baseUrl}/agents/${encodeURIComponent(this.opts.agentId)}/analytics/events`;
832
+ const ac = new AbortController();
833
+ const timer = setTimeout(() => ac.abort(), REQUEST_TIMEOUT_MS2);
834
+ try {
835
+ const res = await fetch(url, {
836
+ method: "POST",
837
+ headers: {
838
+ Authorization: `Bearer ${this.opts.apiKey}`,
839
+ "Content-Type": "application/json"
840
+ },
841
+ body: JSON.stringify({ events: batch }),
842
+ signal: ac.signal
843
+ });
844
+ if (res.ok) {
845
+ this.backoffMs = 1e3;
846
+ } else {
847
+ this.requeueWithBackoff(batch);
848
+ }
849
+ } catch {
850
+ this.requeueWithBackoff(batch);
851
+ } finally {
852
+ clearTimeout(timer);
853
+ }
854
+ }
855
+ requeueWithBackoff(events) {
856
+ if (this.closed) return;
857
+ const space = MAX_QUEUE_SIZE2 - this.buffer.length;
858
+ if (space > 0) {
859
+ this.buffer.unshift(...events.slice(0, space));
860
+ }
861
+ const wait = Math.min(this.backoffMs, MAX_BACKOFF_MS);
862
+ this.backoffMs = Math.min(this.backoffMs * 2, MAX_BACKOFF_MS);
863
+ this.flushTimer = setTimeout(() => {
864
+ this.flushTimer = void 0;
865
+ void this.flush();
866
+ }, wait);
867
+ }
868
+ };
869
+
870
+ // src/llm-content-redactor.ts
871
+ function stripGenAiContentAttributes(attrs) {
872
+ if (!attrs) return attrs;
873
+ const out = { ...attrs };
874
+ for (const key of Object.keys(out)) {
875
+ if (key.startsWith("gen_ai.prompt") || key.startsWith("gen_ai.completion") || key.startsWith("gen_ai.input.") || key.startsWith("gen_ai.output.")) {
876
+ delete out[key];
877
+ }
878
+ }
879
+ return out;
880
+ }
881
+ function withRedactedAttributes(span) {
882
+ return new Proxy(span, {
883
+ get(target, prop, receiver) {
884
+ if (prop === "attributes") {
885
+ const raw = Reflect.get(target, "attributes", receiver);
886
+ return stripGenAiContentAttributes(raw) ?? raw;
887
+ }
888
+ return Reflect.get(target, prop, receiver);
889
+ }
890
+ });
891
+ }
892
+ function createLlmContentRedactor(captureLlmContent, inner) {
893
+ return {
894
+ onStart(span, parentContext) {
895
+ inner.onStart(span, parentContext);
896
+ },
897
+ onEnd(span) {
898
+ if (captureLlmContent) {
899
+ inner.onEnd(span);
900
+ return;
901
+ }
902
+ inner.onEnd(withRedactedAttributes(span));
903
+ },
904
+ shutdown() {
905
+ return inner.shutdown();
906
+ },
907
+ forceFlush() {
908
+ return inner.forceFlush();
909
+ }
910
+ };
911
+ }
912
+
913
+ // src/forwarder.ts
914
+ var FORWARD_TIMEOUT_MS = 2e3;
915
+ function forwardConversationMessage(args, failureBuffer) {
916
+ const url = `${args.baseUrl.replace(/\/$/, "")}/v1/conversations/${encodeURIComponent(
917
+ args.conversationId
918
+ )}/messages`;
919
+ const body = JSON.stringify({
920
+ agentId: args.agentId,
921
+ conversationId: args.conversationId,
922
+ userId: args.userId,
923
+ role: args.role,
924
+ content: args.content,
925
+ ...args.recentMessages && args.recentMessages.length ? { recentMessages: args.recentMessages } : {},
926
+ ...args.model !== void 0 ? { model: args.model } : {},
927
+ ...args.provider !== void 0 ? { provider: args.provider } : {},
928
+ ...args.inputTokens !== void 0 ? { inputTokens: args.inputTokens } : {},
929
+ ...args.outputTokens !== void 0 ? { outputTokens: args.outputTokens } : {},
930
+ ...args.cacheReadTokens !== void 0 ? { cacheReadTokens: args.cacheReadTokens } : {},
931
+ ...args.cacheCreationTokens !== void 0 ? { cacheCreationTokens: args.cacheCreationTokens } : {},
932
+ ...args.latencyMs !== void 0 ? { latencyMs: args.latencyMs } : {},
933
+ ...args.timeToFirstTokenMs !== void 0 ? { timeToFirstTokenMs: args.timeToFirstTokenMs } : {},
934
+ ...args.finishReason !== void 0 ? { finishReason: args.finishReason } : {},
935
+ ...args.isError !== void 0 ? { isError: args.isError } : {},
936
+ ...args.error !== void 0 ? { error: args.error } : {},
937
+ ...args.inputCostUsd !== void 0 ? { inputCostUsd: args.inputCostUsd } : {},
938
+ ...args.outputCostUsd !== void 0 ? { outputCostUsd: args.outputCostUsd } : {},
939
+ ...args.totalCostUsd !== void 0 ? { totalCostUsd: args.totalCostUsd } : {},
940
+ ...args.httpStatus !== void 0 ? { httpStatus: args.httpStatus } : {},
941
+ ...args.properties !== void 0 ? { properties: args.properties } : {}
942
+ });
943
+ void (async () => {
944
+ const ac = new AbortController();
945
+ const timer = setTimeout(() => ac.abort(), FORWARD_TIMEOUT_MS);
946
+ try {
947
+ const res = await fetch(url, {
948
+ method: "POST",
949
+ headers: {
950
+ Authorization: `Bearer ${args.apiKey}`,
951
+ "Content-Type": "application/json"
952
+ },
953
+ body,
954
+ signal: ac.signal
955
+ });
956
+ if (!res.ok) {
957
+ failureBuffer?.push(args);
958
+ }
959
+ } catch {
960
+ failureBuffer?.push(args);
961
+ } finally {
962
+ clearTimeout(timer);
963
+ }
964
+ })();
965
+ }
966
+
967
+ // src/trace-forwarder.ts
968
+ var TRACE_FORWARD_TIMEOUT_MS = 2e3;
969
+ function toIso(d) {
970
+ if (d === void 0 || d === null) return void 0;
971
+ return d instanceof Date ? d.toISOString() : d;
972
+ }
973
+ function serializeSpan(s) {
974
+ return {
975
+ ...s.spanId !== void 0 ? { spanId: s.spanId } : {},
976
+ ...s.parentSpanId !== void 0 ? { parentSpanId: s.parentSpanId } : {},
977
+ ...s.type !== void 0 ? { type: s.type } : {},
978
+ ...s.name !== void 0 ? { name: s.name } : {},
979
+ ...s.model !== void 0 ? { model: s.model } : {},
980
+ ...s.provider !== void 0 ? { provider: s.provider } : {},
981
+ ...s.input !== void 0 ? { input: s.input } : {},
982
+ ...s.output !== void 0 ? { output: s.output } : {},
983
+ ...s.inputTokens !== void 0 ? { inputTokens: s.inputTokens } : {},
984
+ ...s.outputTokens !== void 0 ? { outputTokens: s.outputTokens } : {},
985
+ ...s.cacheReadTokens !== void 0 ? { cacheReadTokens: s.cacheReadTokens } : {},
986
+ ...s.cacheCreationTokens !== void 0 ? { cacheCreationTokens: s.cacheCreationTokens } : {},
987
+ ...s.latencyMs !== void 0 ? { latencyMs: s.latencyMs } : {},
988
+ ...s.timeToFirstTokenMs !== void 0 ? { timeToFirstTokenMs: s.timeToFirstTokenMs } : {},
989
+ ...s.finishReason !== void 0 ? { finishReason: s.finishReason } : {},
990
+ ...s.isError !== void 0 ? { isError: s.isError } : {},
991
+ ...s.error !== void 0 ? { error: s.error } : {},
992
+ ...s.inputCostUsd !== void 0 ? { inputCostUsd: s.inputCostUsd } : {},
993
+ ...s.outputCostUsd !== void 0 ? { outputCostUsd: s.outputCostUsd } : {},
994
+ ...s.totalCostUsd !== void 0 ? { totalCostUsd: s.totalCostUsd } : {},
995
+ ...s.toolsCalled !== void 0 ? { toolsCalled: s.toolsCalled } : {},
996
+ ...s.toolCallCount !== void 0 ? { toolCallCount: s.toolCallCount } : {},
997
+ ...s.inputState !== void 0 ? { inputState: s.inputState } : {},
998
+ ...s.outputState !== void 0 ? { outputState: s.outputState } : {},
999
+ ...s.httpStatus !== void 0 ? { httpStatus: s.httpStatus } : {},
1000
+ ...s.properties !== void 0 ? { properties: s.properties } : {},
1001
+ ...s.startedAt !== void 0 ? { startedAt: toIso(s.startedAt) } : {},
1002
+ ...s.endedAt !== void 0 ? { endedAt: toIso(s.endedAt) } : {}
1003
+ };
1004
+ }
1005
+ function serializeTrace(t) {
1006
+ return {
1007
+ ...t.traceId !== void 0 ? { traceId: t.traceId } : {},
1008
+ ...t.sessionId !== void 0 ? { sessionId: t.sessionId } : {},
1009
+ ...t.userId !== void 0 ? { userId: t.userId } : {},
1010
+ ...t.name !== void 0 ? { name: t.name } : {},
1011
+ ...t.inputState !== void 0 ? { inputState: t.inputState } : {},
1012
+ ...t.outputState !== void 0 ? { outputState: t.outputState } : {},
1013
+ ...t.latencyMs !== void 0 ? { latencyMs: t.latencyMs } : {},
1014
+ ...t.isError !== void 0 ? { isError: t.isError } : {},
1015
+ ...t.error !== void 0 ? { error: t.error } : {},
1016
+ ...t.properties !== void 0 ? { properties: t.properties } : {},
1017
+ ...t.startedAt !== void 0 ? { startedAt: toIso(t.startedAt) } : {},
1018
+ ...t.endedAt !== void 0 ? { endedAt: toIso(t.endedAt) } : {},
1019
+ ...t.spans && t.spans.length > 0 ? { spans: t.spans.map(serializeSpan) } : {}
1020
+ };
1021
+ }
1022
+ function forwardTrace(args) {
1023
+ const url = `${args.baseUrl.replace(/\/$/, "")}/v1/agents/${encodeURIComponent(args.agentId)}/traces`;
1024
+ const body = JSON.stringify(serializeTrace(args.trace));
1025
+ void (async () => {
1026
+ const ac = new AbortController();
1027
+ const timer = setTimeout(() => ac.abort(), TRACE_FORWARD_TIMEOUT_MS);
1028
+ try {
1029
+ await fetch(url, {
1030
+ method: "POST",
1031
+ headers: {
1032
+ Authorization: `Bearer ${args.apiKey}`,
1033
+ "Content-Type": "application/json"
1034
+ },
1035
+ body,
1036
+ signal: ac.signal
1037
+ });
1038
+ } catch {
1039
+ } finally {
1040
+ clearTimeout(timer);
1041
+ }
1042
+ })();
1043
+ }
1044
+
1045
+ // src/outcome-forwarder.ts
1046
+ var OUTCOME_FORWARD_TIMEOUT_MS = 2e3;
1047
+ function forwardOutcome(args) {
1048
+ const url = `${args.baseUrl.replace(/\/$/, "")}/v1/conversations/${encodeURIComponent(args.conversationId)}/signal`;
1049
+ const payload = {};
1050
+ if (args.outcome !== void 0) payload.outcome = args.outcome;
1051
+ if (args.intent !== void 0) payload.intent = args.intent;
1052
+ if (args.userId !== void 0) payload.userId = args.userId;
1053
+ const body = JSON.stringify(payload);
1054
+ void (async () => {
1055
+ const ac = new AbortController();
1056
+ const timer = setTimeout(() => ac.abort(), OUTCOME_FORWARD_TIMEOUT_MS);
1057
+ try {
1058
+ await fetch(url, {
1059
+ method: "PATCH",
1060
+ headers: {
1061
+ Authorization: `Bearer ${args.apiKey}`,
1062
+ "Content-Type": "application/json"
1063
+ },
1064
+ body,
1065
+ signal: ac.signal
1066
+ });
1067
+ } catch {
1068
+ } finally {
1069
+ clearTimeout(timer);
1070
+ }
1071
+ })();
1072
+ }
1073
+
1074
+ // src/feedback-forwarder.ts
1075
+ var FEEDBACK_FORWARD_TIMEOUT_MS = 2e3;
1076
+ function forwardFeedback(args) {
1077
+ const url = `${args.baseUrl.replace(/\/$/, "")}/v1/conversations/${encodeURIComponent(args.conversationId)}/feedback`;
1078
+ const payload = { rating: args.rating };
1079
+ if (args.comment !== void 0) payload.comment = args.comment;
1080
+ if (args.messageId !== void 0) payload.messageId = args.messageId;
1081
+ if (args.userId !== void 0) payload.userId = args.userId;
1082
+ const body = JSON.stringify(payload);
1083
+ void (async () => {
1084
+ const ac = new AbortController();
1085
+ const timer = setTimeout(() => ac.abort(), FEEDBACK_FORWARD_TIMEOUT_MS);
1086
+ try {
1087
+ await fetch(url, {
1088
+ method: "POST",
1089
+ headers: {
1090
+ Authorization: `Bearer ${args.apiKey}`,
1091
+ "Content-Type": "application/json"
1092
+ },
1093
+ body,
1094
+ signal: ac.signal
1095
+ });
1096
+ } catch {
1097
+ } finally {
1098
+ clearTimeout(timer);
1099
+ }
1100
+ })();
1101
+ }
1102
+
1103
+ // src/ring-buffer.ts
1104
+ var DEFAULT_MAX = 1e3;
1105
+ var RingBuffer = class {
1106
+ items = [];
1107
+ max;
1108
+ constructor(maxSize = DEFAULT_MAX) {
1109
+ this.max = maxSize;
1110
+ }
1111
+ push(item) {
1112
+ if (this.items.length >= this.max) {
1113
+ this.items.shift();
1114
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
1115
+ console.warn("[@firstflow/core] ring buffer overflow \u2014 dropped oldest entry");
1116
+ }
1117
+ }
1118
+ this.items.push(item);
1119
+ }
1120
+ drainAll() {
1121
+ const out = this.items.slice();
1122
+ this.items.length = 0;
1123
+ return out;
1124
+ }
1125
+ size() {
1126
+ return this.items.length;
1127
+ }
1128
+ };
1129
+
1130
+ // src/model-pricing.ts
1131
+ var PRICING = {
1132
+ // -------------------------------------------------------------------------
1133
+ // OpenAI
1134
+ // -------------------------------------------------------------------------
1135
+ "gpt-4o": { inputPer1M: 2.5, outputPer1M: 10 },
1136
+ "gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6 },
1137
+ "gpt-4o-audio-preview": { inputPer1M: 2.5, outputPer1M: 10 },
1138
+ "gpt-4-turbo": { inputPer1M: 10, outputPer1M: 30 },
1139
+ "gpt-4": { inputPer1M: 30, outputPer1M: 60 },
1140
+ "gpt-3.5-turbo": { inputPer1M: 0.5, outputPer1M: 1.5 },
1141
+ "o1": { inputPer1M: 15, outputPer1M: 60 },
1142
+ "o1-mini": { inputPer1M: 1.1, outputPer1M: 4.4 },
1143
+ "o1-preview": { inputPer1M: 15, outputPer1M: 60 },
1144
+ "o3": { inputPer1M: 10, outputPer1M: 40 },
1145
+ "o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4 },
1146
+ "o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4 },
1147
+ // -------------------------------------------------------------------------
1148
+ // Anthropic
1149
+ // -------------------------------------------------------------------------
1150
+ "claude-opus-4-5": { inputPer1M: 15, outputPer1M: 75 },
1151
+ "claude-opus-4-7": { inputPer1M: 15, outputPer1M: 75 },
1152
+ "claude-sonnet-4-5": { inputPer1M: 3, outputPer1M: 15 },
1153
+ "claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15 },
1154
+ "claude-haiku-4-5": { inputPer1M: 0.8, outputPer1M: 4 },
1155
+ "claude-3-5-sonnet-20241022": { inputPer1M: 3, outputPer1M: 15 },
1156
+ "claude-3-5-sonnet-20240620": { inputPer1M: 3, outputPer1M: 15 },
1157
+ "claude-3-5-haiku-20241022": { inputPer1M: 0.8, outputPer1M: 4 },
1158
+ "claude-3-opus-20240229": { inputPer1M: 15, outputPer1M: 75 },
1159
+ "claude-3-sonnet-20240229": { inputPer1M: 3, outputPer1M: 15 },
1160
+ "claude-3-haiku-20240307": { inputPer1M: 0.25, outputPer1M: 1.25 }
1161
+ };
1162
+ function findByPrefix(model) {
1163
+ let candidate = model;
1164
+ while (candidate.includes("-")) {
1165
+ candidate = candidate.replace(/-[^-]+$/, "");
1166
+ const match = Object.keys(PRICING).find((k) => k.startsWith(candidate));
1167
+ if (match) return PRICING[match];
1168
+ }
1169
+ return void 0;
1170
+ }
1171
+ function calculateCost(model, inputTokens, outputTokens) {
1172
+ const pricing = PRICING[model] ?? findByPrefix(model);
1173
+ if (!pricing) return null;
1174
+ const inputCostUsd = inputTokens / 1e6 * pricing.inputPer1M;
1175
+ const outputCostUsd = outputTokens / 1e6 * pricing.outputPer1M;
1176
+ return { inputCostUsd, outputCostUsd, totalCostUsd: inputCostUsd + outputCostUsd };
1177
+ }
1178
+
1179
+ // src/constants.ts
1180
+ var DEFAULT_FIRSTFLOW_BASE_URL = "https://api.firstflow.app";
1181
+ var FIRSTFLOW_SERVER_PACKAGE_VERSION = "0.0.1-alpha.0";
1182
+
1183
+ // src/peer-deps.ts
1184
+ var import_node_module = require("module");
1185
+ var nodeRequire = (0, import_node_module.createRequire)(importMetaUrl);
1186
+ function requirePeer(moduleName) {
1187
+ return nodeRequire(moduleName);
1188
+ }
1189
+ function isPeerPreloaded(moduleName) {
1190
+ try {
1191
+ const resolved = nodeRequire.resolve(moduleName);
1192
+ const cached = nodeRequire.cache?.[resolved];
1193
+ return cached !== void 0 && cached.exports !== void 0;
1194
+ } catch {
1195
+ return false;
1196
+ }
1197
+ }
1198
+
1199
+ // src/runtime.ts
1200
+ function enrichSpanCosts(span) {
1201
+ if (span.inputCostUsd !== void 0 && span.outputCostUsd !== void 0) return span;
1202
+ if (!span.model || typeof span.inputTokens !== "number" || typeof span.outputTokens !== "number") {
1203
+ return span;
1204
+ }
1205
+ const cost = calculateCost(span.model, span.inputTokens, span.outputTokens);
1206
+ if (!cost) return span;
1207
+ return {
1208
+ ...span,
1209
+ inputCostUsd: span.inputCostUsd ?? cost.inputCostUsd,
1210
+ outputCostUsd: span.outputCostUsd ?? cost.outputCostUsd,
1211
+ totalCostUsd: span.totalCostUsd ?? cost.totalCostUsd
1212
+ };
1213
+ }
1214
+ function readCaptureLlmContent() {
1215
+ return process.env.FIRSTFLOW_CAPTURE_LLM_CONTENT?.trim().toLowerCase() === "true";
1216
+ }
1217
+ var Runtime = class {
1218
+ apiKey;
1219
+ baseUrl;
1220
+ forwardFailures = new RingBuffer(1e3);
1221
+ analyticsByAgent = /* @__PURE__ */ new Map();
1222
+ otelSdk;
1223
+ closed = false;
1224
+ constructor(apiKey, baseUrl) {
1225
+ this.apiKey = apiKey;
1226
+ this.baseUrl = baseUrl;
1227
+ this.startOpenTelemetry();
1228
+ process.once("beforeExit", () => {
1229
+ void this.shutdown();
1230
+ });
1231
+ }
1232
+ startOpenTelemetry() {
1233
+ const captureLlmContent = readCaptureLlmContent();
1234
+ const inner = new FirstflowSpanProcessor({ apiKey: this.apiKey, baseUrl: this.baseUrl });
1235
+ const chain = createLlmContentRedactor(captureLlmContent, inner);
1236
+ const resource = (0, import_resources.resourceFromAttributes)({ "service.name": "firstflow-server" });
1237
+ const anthropicInstrumentation = new import_instrumentation_anthropic.AnthropicInstrumentation({ traceContent: captureLlmContent });
1238
+ const anthropicPreloaded = isPeerPreloaded("@anthropic-ai/sdk");
1239
+ this.otelSdk = new import_sdk_node.NodeSDK({
1240
+ resource,
1241
+ spanProcessors: [chain],
1242
+ instrumentations: [
1243
+ new import_instrumentation_openai.OpenAIInstrumentation({ captureMessageContent: captureLlmContent }),
1244
+ anthropicInstrumentation
1245
+ ]
1246
+ });
1247
+ this.otelSdk.start();
1248
+ if (anthropicPreloaded) {
1249
+ try {
1250
+ anthropicInstrumentation.manuallyInstrument(requirePeer("@anthropic-ai/sdk"));
1251
+ } catch {
1252
+ }
1253
+ }
1254
+ }
1255
+ analyticsFor(agentId) {
1256
+ let client = this.analyticsByAgent.get(agentId);
1257
+ if (!client) {
1258
+ client = new FirstflowAnalyticsClient({
1259
+ apiKey: this.apiKey,
1260
+ baseUrl: this.baseUrl,
1261
+ agentId,
1262
+ sdkVersion: FIRSTFLOW_SERVER_PACKAGE_VERSION
1263
+ });
1264
+ this.analyticsByAgent.set(agentId, client);
1265
+ }
1266
+ return client;
1267
+ }
1268
+ /**
1269
+ * Build a request-agnostic `WrapContext` for the proxy. The context carries
1270
+ * only infrastructure hooks — the per-request agent/user/session are parsed
1271
+ * from each call and flow through `ObserveArgs`, never stored here.
1272
+ */
1273
+ wrapContext() {
1274
+ return {
1275
+ apiKey: this.apiKey,
1276
+ baseUrl: this.baseUrl,
1277
+ onAssistantComplete: (a) => this.observe(a),
1278
+ onUserMessage: (a) => this.observe(a),
1279
+ onError: (a) => {
1280
+ if (!a.conversationId) return;
1281
+ this.observe({
1282
+ agentId: a.agentId,
1283
+ conversationId: a.conversationId,
1284
+ userId: a.userId,
1285
+ role: "assistant",
1286
+ content: "",
1287
+ isError: true,
1288
+ error: a.error,
1289
+ model: a.model,
1290
+ provider: a.provider,
1291
+ latencyMs: a.latencyMs,
1292
+ httpStatus: a.httpStatus
1293
+ });
1294
+ }
1295
+ };
1296
+ }
1297
+ observe(args) {
1298
+ let { inputCostUsd, outputCostUsd, totalCostUsd } = args;
1299
+ if (totalCostUsd === void 0 && args.model && typeof args.inputTokens === "number" && typeof args.outputTokens === "number") {
1300
+ const cost = calculateCost(args.model, args.inputTokens, args.outputTokens);
1301
+ if (cost) {
1302
+ inputCostUsd = cost.inputCostUsd;
1303
+ outputCostUsd = cost.outputCostUsd;
1304
+ totalCostUsd = cost.totalCostUsd;
1305
+ }
1306
+ }
1307
+ forwardConversationMessage(
1308
+ {
1309
+ baseUrl: this.baseUrl,
1310
+ apiKey: this.apiKey,
1311
+ agentId: args.agentId,
1312
+ conversationId: args.conversationId,
1313
+ userId: args.userId,
1314
+ role: args.role,
1315
+ content: args.content,
1316
+ model: args.model,
1317
+ provider: args.provider,
1318
+ inputTokens: args.inputTokens,
1319
+ outputTokens: args.outputTokens,
1320
+ cacheReadTokens: args.cacheReadTokens,
1321
+ cacheCreationTokens: args.cacheCreationTokens,
1322
+ latencyMs: args.latencyMs,
1323
+ timeToFirstTokenMs: args.timeToFirstTokenMs,
1324
+ finishReason: args.finishReason,
1325
+ isError: args.isError,
1326
+ error: args.error,
1327
+ inputCostUsd,
1328
+ outputCostUsd,
1329
+ totalCostUsd,
1330
+ httpStatus: args.httpStatus,
1331
+ ...args.recentMessages ? { recentMessages: args.recentMessages } : {},
1332
+ ...args.properties ? { properties: args.properties } : {}
1333
+ },
1334
+ this.forwardFailures
1335
+ );
1336
+ this.analyticsFor(args.agentId).track(args.userId, "conversation_message", {
1337
+ conversation_id: args.conversationId,
1338
+ role: args.role,
1339
+ has_content: args.content.length > 0
1340
+ });
1341
+ }
1342
+ trace(agentId, input) {
1343
+ const enriched = input.spans ? { ...input, spans: input.spans.map(enrichSpanCosts) } : input;
1344
+ forwardTrace({ baseUrl: this.baseUrl, apiKey: this.apiKey, agentId, trace: enriched });
1345
+ }
1346
+ outcome(args) {
1347
+ forwardOutcome({ baseUrl: this.baseUrl, apiKey: this.apiKey, ...args });
1348
+ }
1349
+ feedback(args) {
1350
+ forwardFeedback({ baseUrl: this.baseUrl, apiKey: this.apiKey, ...args });
1351
+ }
1352
+ track(agentId, userId, event, properties) {
1353
+ this.analyticsFor(agentId).track(
1354
+ userId,
1355
+ event,
1356
+ properties ? sanitizeAnalyticsProperties({ ...properties }) : void 0
1357
+ );
1358
+ }
1359
+ identify(agentId, userId, traits) {
1360
+ this.analyticsFor(agentId).identify(
1361
+ userId,
1362
+ traits ? sanitizeAnalyticsProperties({ ...traits }) : void 0
1363
+ );
1364
+ }
1365
+ async shutdown() {
1366
+ if (this.closed) return;
1367
+ this.closed = true;
1368
+ if (this.otelSdk) {
1369
+ await this.otelSdk.shutdown().catch(() => {
1370
+ });
1371
+ this.otelSdk = void 0;
1372
+ }
1373
+ for (const client of this.analyticsByAgent.values()) {
1374
+ await client.shutdown().catch(() => {
1375
+ });
1376
+ }
1377
+ this.analyticsByAgent.clear();
1378
+ const pending = this.forwardFailures.drainAll();
1379
+ for (const p of pending) forwardConversationMessage(p, void 0);
1380
+ }
1381
+ };
1382
+ var _runtime;
1383
+ function getRuntime() {
1384
+ if (_runtime) return _runtime;
1385
+ const apiKey = (process.env.FIRSTFLOW_API_KEY ?? "").trim();
1386
+ if (!apiKey) {
1387
+ throw new Error(
1388
+ "[Firstflow] FIRSTFLOW_API_KEY is required. Set it in your server environment."
1389
+ );
1390
+ }
1391
+ const baseUrl = (process.env.FIRSTFLOW_API_BASE_URL ?? DEFAULT_FIRSTFLOW_BASE_URL).replace(/\/$/, "");
1392
+ _runtime = new Runtime(apiKey, baseUrl);
1393
+ return _runtime;
1394
+ }
1395
+
1396
+ // src/standalone.ts
1397
+ function observe(input) {
1398
+ const { firstflowAgentId, sessionId, userId, ...rest } = input;
1399
+ getRuntime().observe({
1400
+ agentId: firstflowAgentId,
1401
+ conversationId: sessionId,
1402
+ userId,
1403
+ ...rest
1404
+ });
1405
+ }
1406
+ function outcome(input) {
1407
+ getRuntime().outcome({
1408
+ conversationId: input.sessionId,
1409
+ outcome: input.outcome,
1410
+ intent: input.intent,
1411
+ userId: input.userId
1412
+ });
1413
+ }
1414
+ function feedback(input) {
1415
+ getRuntime().feedback({
1416
+ conversationId: input.sessionId,
1417
+ rating: input.rating,
1418
+ comment: input.comment,
1419
+ messageId: input.messageId,
1420
+ userId: input.userId
1421
+ });
1422
+ }
1423
+ function track(args) {
1424
+ getRuntime().track(args.firstflowAgentId, args.userId, args.event, args.properties);
1425
+ }
1426
+ function identify(args) {
1427
+ getRuntime().identify(args.firstflowAgentId, args.userId, args.traits);
1428
+ }
1429
+ function trace(args) {
1430
+ const { firstflowAgentId, ...input } = args;
1431
+ getRuntime().trace(firstflowAgentId, input);
1432
+ }
1433
+
1434
+ // src/wrap.ts
1435
+ var INSTRUMENTED_PATHS = /* @__PURE__ */ new Set([
1436
+ "chat.completions.create",
1437
+ "embeddings.create",
1438
+ "responses.create",
1439
+ "messages.create"
1440
+ ]);
1441
+ var warnedIncompleteTagging = false;
1442
+ function parseFirstflowMeta(args) {
1443
+ const nextArgs = [...args];
1444
+ for (let i = 0; i < nextArgs.length; i++) {
1445
+ const a = nextArgs[i];
1446
+ if (typeof a !== "object" || a === null) continue;
1447
+ const rec = a;
1448
+ if (!("firstflowAgentId" in rec) && !("sessionId" in rec) && !("userId" in rec)) {
1449
+ continue;
1450
+ }
1451
+ const agentId = typeof rec.firstflowAgentId === "string" ? rec.firstflowAgentId.trim() : "";
1452
+ const sessionId = typeof rec.sessionId === "string" ? rec.sessionId.trim() : "";
1453
+ const userId = typeof rec.userId === "string" ? rec.userId.trim() : "";
1454
+ const { firstflowAgentId: _a, sessionId: _s, userId: _u, ...rest } = rec;
1455
+ nextArgs[i] = rest;
1456
+ if (!agentId || !sessionId || !userId) {
1457
+ if (!warnedIncompleteTagging) {
1458
+ warnedIncompleteTagging = true;
1459
+ console.warn(
1460
+ "[@firstflow/core] LLM call tagged with some but not all of `firstflowAgentId`, `sessionId`, `userId` \u2014 the call was NOT observed. Provide all three to track it."
1461
+ );
1462
+ }
1463
+ return { nextArgs };
1464
+ }
1465
+ return { nextArgs, meta: { agentId, userId, conversationId: sessionId } };
1466
+ }
1467
+ return { nextArgs };
1468
+ }
1469
+ function extractModel(nextArgs) {
1470
+ const body = nextArgs[0];
1471
+ if (!body || typeof body !== "object") return void 0;
1472
+ const m = body.model;
1473
+ return typeof m === "string" ? m : void 0;
1474
+ }
1475
+ function detectProvider(path) {
1476
+ return path === "messages.create" ? "anthropic" : "openai";
1477
+ }
1478
+ function injectOpenAiStreamUsage(nextArgs) {
1479
+ const body = nextArgs[0];
1480
+ if (!body || typeof body !== "object") return nextArgs;
1481
+ const b = body;
1482
+ if (b.stream !== true) return nextArgs;
1483
+ const existing = b.stream_options ?? {};
1484
+ if (existing.include_usage === true) return nextArgs;
1485
+ const patched = [...nextArgs];
1486
+ patched[0] = { ...b, stream_options: { ...existing, include_usage: true } };
1487
+ return patched;
1488
+ }
1489
+ function extractOpenAiResponseMeta(res) {
1490
+ const usage = res.usage;
1491
+ const details = usage?.prompt_tokens_details;
1492
+ const choice = Array.isArray(res.choices) ? res.choices[0] : void 0;
1493
+ return {
1494
+ inputTokens: typeof usage?.prompt_tokens === "number" ? usage.prompt_tokens : void 0,
1495
+ outputTokens: typeof usage?.completion_tokens === "number" ? usage.completion_tokens : void 0,
1496
+ cacheReadTokens: typeof details?.cached_tokens === "number" ? details.cached_tokens : void 0,
1497
+ finishReason: typeof choice?.finish_reason === "string" ? choice.finish_reason : void 0
1498
+ };
1499
+ }
1500
+ function extractAnthropicResponseMeta(res) {
1501
+ const usage = res.usage;
1502
+ return {
1503
+ inputTokens: typeof usage?.input_tokens === "number" ? usage.input_tokens : void 0,
1504
+ outputTokens: typeof usage?.output_tokens === "number" ? usage.output_tokens : void 0,
1505
+ cacheReadTokens: typeof usage?.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : void 0,
1506
+ cacheCreationTokens: typeof usage?.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : void 0,
1507
+ finishReason: typeof res.stop_reason === "string" ? res.stop_reason : void 0
1508
+ };
1509
+ }
1510
+ function accumOpenAiStreamChunkMeta(chunk, acc) {
1511
+ if (!chunk || typeof chunk !== "object") return;
1512
+ const c = chunk;
1513
+ if (c.usage && typeof c.usage === "object") {
1514
+ const usage = c.usage;
1515
+ const details = usage.prompt_tokens_details;
1516
+ if (typeof usage.prompt_tokens === "number") acc.inputTokens = usage.prompt_tokens;
1517
+ if (typeof usage.completion_tokens === "number") acc.outputTokens = usage.completion_tokens;
1518
+ if (typeof details?.cached_tokens === "number") acc.cacheReadTokens = details.cached_tokens;
1519
+ }
1520
+ if (Array.isArray(c.choices) && c.choices[0]) {
1521
+ const choice = c.choices[0];
1522
+ if (typeof choice.finish_reason === "string" && choice.finish_reason) {
1523
+ acc.finishReason = choice.finish_reason;
1524
+ }
1525
+ }
1526
+ }
1527
+ function accumAnthropicStreamChunkMeta(chunk, acc) {
1528
+ if (!chunk || typeof chunk !== "object") return;
1529
+ const c = chunk;
1530
+ if (c.type === "message_start") {
1531
+ const msg = c.message;
1532
+ const usage = msg?.usage;
1533
+ if (typeof usage?.input_tokens === "number") acc.inputTokens = usage.input_tokens;
1534
+ if (typeof usage?.cache_read_input_tokens === "number")
1535
+ acc.cacheReadTokens = usage.cache_read_input_tokens;
1536
+ if (typeof usage?.cache_creation_input_tokens === "number")
1537
+ acc.cacheCreationTokens = usage.cache_creation_input_tokens;
1538
+ }
1539
+ if (c.type === "message_delta") {
1540
+ const usage = c.usage;
1541
+ const delta = c.delta;
1542
+ if (typeof usage?.output_tokens === "number") acc.outputTokens = usage.output_tokens;
1543
+ if (typeof delta?.stop_reason === "string") acc.finishReason = delta.stop_reason;
1544
+ }
1545
+ }
1546
+ function isAsyncIterable(v) {
1547
+ return typeof v === "object" && v !== null && Symbol.asyncIterator in v && typeof v[Symbol.asyncIterator] === "function";
1548
+ }
1549
+ function streamChunkText(chunk) {
1550
+ if (!chunk || typeof chunk !== "object") return "";
1551
+ const c = chunk;
1552
+ if (Array.isArray(c.choices)) {
1553
+ const choice = c.choices[0];
1554
+ if (choice && typeof choice === "object") {
1555
+ const delta = choice.delta;
1556
+ if (delta && typeof delta === "object") {
1557
+ const content = delta.content;
1558
+ if (typeof content === "string") return content;
1559
+ }
1560
+ }
1561
+ }
1562
+ if (c.type === "content_block_delta") {
1563
+ const delta = c.delta;
1564
+ if (delta && typeof delta === "object") {
1565
+ const d = delta;
1566
+ if (d.type === "text_delta" && typeof d.text === "string") return d.text;
1567
+ }
1568
+ }
1569
+ return "";
1570
+ }
1571
+ function wrapAsyncIterableForAssistantHook(stream, hook, provider, startTime) {
1572
+ return {
1573
+ async *[Symbol.asyncIterator]() {
1574
+ let content = "";
1575
+ let firstChunkTime = -1;
1576
+ let streamError;
1577
+ const acc = {};
1578
+ const accumChunkMeta = provider === "anthropic" ? accumAnthropicStreamChunkMeta : accumOpenAiStreamChunkMeta;
1579
+ try {
1580
+ for await (const chunk of stream) {
1581
+ if (firstChunkTime === -1) firstChunkTime = Date.now();
1582
+ content += streamChunkText(chunk);
1583
+ accumChunkMeta(chunk, acc);
1584
+ yield chunk;
1585
+ }
1586
+ } catch (err) {
1587
+ streamError = err;
1588
+ throw err;
1589
+ } finally {
1590
+ hook(content, {
1591
+ ...acc,
1592
+ httpStatus: streamError != null ? streamError.status ?? 500 : 200,
1593
+ latencyMs: Date.now() - startTime,
1594
+ timeToFirstTokenMs: firstChunkTime === -1 ? void 0 : firstChunkTime - startTime
1595
+ });
1596
+ }
1597
+ }
1598
+ };
1599
+ }
1600
+ function firstArgBodyStream(nextArgs) {
1601
+ const body = nextArgs[0];
1602
+ return !!(body && typeof body === "object" && body !== null && body.stream === true);
1603
+ }
1604
+ function extractAnthropicAssistantText(res) {
1605
+ const content = res.content;
1606
+ if (!Array.isArray(content)) return "";
1607
+ let acc = "";
1608
+ for (const item of content) {
1609
+ if (item && typeof item === "object" && !Array.isArray(item)) {
1610
+ const o = item;
1611
+ if (o.type === "text" && typeof o.text === "string") {
1612
+ acc += o.text;
1613
+ }
1614
+ }
1615
+ }
1616
+ return acc;
1617
+ }
1618
+ function extractOpenAiAssistantText(res) {
1619
+ if (!Array.isArray(res.choices) || !res.choices[0] || typeof res.choices[0] !== "object") {
1620
+ return "";
1621
+ }
1622
+ const message = res.choices[0].message;
1623
+ return typeof message?.content === "string" ? message.content : "";
1624
+ }
1625
+ function isThenable(v) {
1626
+ return !!v && (typeof v === "object" || typeof v === "function") && "then" in v && typeof v.then === "function";
1627
+ }
1628
+ function messageContentToText(content) {
1629
+ if (typeof content === "string") return content;
1630
+ if (!Array.isArray(content)) return "";
1631
+ let acc = "";
1632
+ for (const part of content) {
1633
+ if (part && typeof part === "object" && !Array.isArray(part)) {
1634
+ const p = part;
1635
+ if (p.type === "text" && typeof p.text === "string") acc += p.text;
1636
+ }
1637
+ }
1638
+ return acc;
1639
+ }
1640
+ function extractRecentMessages(nextArgs) {
1641
+ const body = nextArgs[0];
1642
+ if (!body || typeof body !== "object") return [];
1643
+ const messages = body.messages;
1644
+ if (!Array.isArray(messages)) return [];
1645
+ const out = [];
1646
+ for (const m of messages) {
1647
+ if (!m || typeof m !== "object") continue;
1648
+ const rec = m;
1649
+ if (rec.role !== "user" && rec.role !== "assistant") continue;
1650
+ const content = messageContentToText(rec.content).trim();
1651
+ if (!content) continue;
1652
+ out.push({ role: rec.role, content });
1653
+ }
1654
+ return out;
1655
+ }
1656
+ function extractTurnStartUserMessage(nextArgs) {
1657
+ const body = nextArgs[0];
1658
+ if (!body || typeof body !== "object") return void 0;
1659
+ const messages = body.messages;
1660
+ if (!Array.isArray(messages) || messages.length === 0) return void 0;
1661
+ const last = messages[messages.length - 1];
1662
+ if (!last || typeof last !== "object") return void 0;
1663
+ const rec = last;
1664
+ if (rec.role !== "user") return void 0;
1665
+ const text = messageContentToText(rec.content).trim();
1666
+ return text || void 0;
1667
+ }
1668
+ function withFirstflowBaggage(meta, fn) {
1669
+ const base = propagation.getBaggage(context.active()) ?? propagation.createBaggage();
1670
+ const bag = base.setEntry("firstflow.user_id", { value: meta.userId }).setEntry("firstflow.agent_id", { value: meta.agentId });
1671
+ return context.with(propagation.setBaggage(context.active(), bag), fn);
1672
+ }
1673
+ function maybeObserveUserMessage(path, nextArgs, meta, ctx) {
1674
+ if (!ctx.onUserMessage) return;
1675
+ if (path !== "chat.completions.create" && path !== "messages.create") return;
1676
+ if (!meta.conversationId) return;
1677
+ const content = extractTurnStartUserMessage(nextArgs);
1678
+ if (!content) return;
1679
+ const recentMessages = extractRecentMessages(nextArgs);
1680
+ ctx.onUserMessage({
1681
+ agentId: meta.agentId,
1682
+ conversationId: meta.conversationId,
1683
+ userId: meta.userId,
1684
+ role: "user",
1685
+ content,
1686
+ model: extractModel(nextArgs),
1687
+ provider: detectProvider(path),
1688
+ ...recentMessages.length ? { recentMessages } : {}
1689
+ });
1690
+ }
1691
+ function maybeAttachAssistantHook(path, nextArgs, result, meta, ctx, startTime) {
1692
+ if (!ctx.onAssistantComplete) return void 0;
1693
+ if (path !== "chat.completions.create" && path !== "messages.create") {
1694
+ return void 0;
1695
+ }
1696
+ const convId = meta.conversationId;
1697
+ const recentMessages = extractRecentMessages(nextArgs);
1698
+ const model = extractModel(nextArgs);
1699
+ const provider = detectProvider(path);
1700
+ const extractFull = path === "chat.completions.create" ? extractOpenAiAssistantText : extractAnthropicAssistantText;
1701
+ const extractMeta = path === "chat.completions.create" ? extractOpenAiResponseMeta : extractAnthropicResponseMeta;
1702
+ const emit = (content, llmMeta) => {
1703
+ if (!convId) return;
1704
+ ctx.onAssistantComplete?.({
1705
+ agentId: meta.agentId,
1706
+ conversationId: convId,
1707
+ userId: meta.userId,
1708
+ role: "assistant",
1709
+ content,
1710
+ model,
1711
+ provider,
1712
+ ...llmMeta,
1713
+ ...recentMessages.length ? { recentMessages } : {}
1714
+ });
1715
+ };
1716
+ if (firstArgBodyStream(nextArgs)) {
1717
+ const wrapStream = (stream) => wrapAsyncIterableForAssistantHook(
1718
+ stream,
1719
+ (content, streamMeta) => emit(content, streamMeta),
1720
+ provider,
1721
+ startTime
1722
+ );
1723
+ if (isAsyncIterable(result)) {
1724
+ return wrapStream(result);
1725
+ }
1726
+ if (isThenable(result)) {
1727
+ return result.then(
1728
+ (resolved) => isAsyncIterable(resolved) ? wrapStream(resolved) : resolved
1729
+ );
1730
+ }
1731
+ return void 0;
1732
+ }
1733
+ if (isThenable(result)) {
1734
+ return result.then((res) => {
1735
+ const latencyMs = Date.now() - startTime;
1736
+ const resMeta = extractMeta(res ?? {});
1737
+ emit(extractFull(res ?? {}), { ...resMeta, latencyMs, httpStatus: 200 });
1738
+ return res;
1739
+ });
1740
+ }
1741
+ return void 0;
1742
+ }
1743
+ function wrapInstrumentedInvocation(path, original, thisArg, args, ctx) {
1744
+ const parsed = parseFirstflowMeta(args);
1745
+ const { meta } = parsed;
1746
+ let nextArgs = parsed.nextArgs;
1747
+ if (path === "chat.completions.create") {
1748
+ nextArgs = injectOpenAiStreamUsage(nextArgs);
1749
+ }
1750
+ const startTime = Date.now();
1751
+ const run = () => {
1752
+ if (meta) maybeObserveUserMessage(path, nextArgs, meta, ctx);
1753
+ let result;
1754
+ try {
1755
+ result = Reflect.apply(original, thisArg, nextArgs);
1756
+ } catch (err) {
1757
+ if (meta?.conversationId && ctx.onError) {
1758
+ ctx.onError({
1759
+ agentId: meta.agentId,
1760
+ conversationId: meta.conversationId,
1761
+ userId: meta.userId,
1762
+ isError: true,
1763
+ error: err instanceof Error ? err.message : String(err),
1764
+ model: extractModel(nextArgs),
1765
+ provider: detectProvider(path),
1766
+ latencyMs: Date.now() - startTime,
1767
+ httpStatus: err.status
1768
+ });
1769
+ }
1770
+ throw err;
1771
+ }
1772
+ if (isThenable(result) && meta?.conversationId && ctx.onError) {
1773
+ result = result.catch((err) => {
1774
+ ctx.onError({
1775
+ agentId: meta.agentId,
1776
+ conversationId: meta.conversationId,
1777
+ userId: meta.userId,
1778
+ isError: true,
1779
+ error: err instanceof Error ? err.message : String(err),
1780
+ model: extractModel(nextArgs),
1781
+ provider: detectProvider(path),
1782
+ latencyMs: Date.now() - startTime,
1783
+ httpStatus: err.status
1784
+ });
1785
+ return Promise.reject(err);
1786
+ });
1787
+ }
1788
+ if (meta) {
1789
+ const hooked = maybeAttachAssistantHook(path, nextArgs, result, meta, ctx, startTime);
1790
+ if (hooked !== void 0) return hooked;
1791
+ }
1792
+ return result;
1793
+ };
1794
+ if (meta) {
1795
+ return withFirstflowBaggage(meta, run);
1796
+ }
1797
+ return run();
1798
+ }
1799
+ function createNestedProxy(target, path, ctx) {
1800
+ return new Proxy(target, {
1801
+ get(_t, prop, receiver) {
1802
+ const key = String(prop);
1803
+ const value = Reflect.get(target, prop, receiver);
1804
+ if (typeof value === "function") {
1805
+ const joined = [...path, key].join(".");
1806
+ return (...fnArgs) => {
1807
+ if (INSTRUMENTED_PATHS.has(joined)) {
1808
+ return wrapInstrumentedInvocation(
1809
+ joined,
1810
+ value,
1811
+ target,
1812
+ fnArgs,
1813
+ ctx
1814
+ );
1815
+ }
1816
+ return Reflect.apply(value, target, fnArgs);
1817
+ };
1818
+ }
1819
+ if (value !== null && typeof value === "object") {
1820
+ return createNestedProxy(value, [...path, key], ctx);
1821
+ }
1822
+ return value;
1823
+ }
1824
+ });
1825
+ }
1826
+ function wrapClient(client, ctx) {
1827
+ return createNestedProxy(client, [], ctx);
1828
+ }
1829
+
1830
+ // src/agents.ts
1831
+ async function listAccessibleAgents(opts) {
1832
+ const base = (opts.baseUrl ?? DEFAULT_FIRSTFLOW_BASE_URL).replace(/\/$/, "");
1833
+ const apiKey = opts.apiKey.trim();
1834
+ if (!apiKey) return { agents: [], workspaceId: "", publishableKey: null };
1835
+ const res = await fetch(`${base}/v1/agents/accessible`, {
1836
+ method: "GET",
1837
+ headers: { Authorization: `Bearer ${apiKey}` },
1838
+ cache: "no-store"
1839
+ });
1840
+ if (!res.ok) {
1841
+ const detail = await res.text().catch(() => "");
1842
+ throw new Error(
1843
+ `[Firstflow] listAccessibleAgents: HTTP ${res.status} \u2014 ${detail.slice(0, 200)}`
1844
+ );
1845
+ }
1846
+ const json = await res.json();
1847
+ return {
1848
+ agents: (json.data ?? []).map((a) => ({ id: a.id, name: a.name ?? null })),
1849
+ workspaceId: json.workspaceId ?? "",
1850
+ publishableKey: json.publishableKey ?? null
1851
+ };
1852
+ }
1853
+ // Annotate the CommonJS export names for ESM import in node:
1854
+ 0 && (module.exports = {
1855
+ DEFAULT_FIRSTFLOW_BASE_URL,
1856
+ FIRSTFLOW_SERVER_PACKAGE_VERSION,
1857
+ feedback,
1858
+ identify,
1859
+ listAccessibleAgents,
1860
+ observe,
1861
+ outcome,
1862
+ trace,
1863
+ track,
1864
+ wrapClient
1865
+ });