@prefactor/core 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +35 -2
  2. package/dist/agent/instance-manager.d.ts +19 -0
  3. package/dist/agent/instance-manager.d.ts.map +1 -0
  4. package/dist/agent/instance-manager.js +71 -0
  5. package/dist/agent/instance-manager.js.map +1 -0
  6. package/dist/agent/schema-registry.d.ts +9 -0
  7. package/dist/agent/schema-registry.d.ts.map +1 -0
  8. package/dist/agent/schema-registry.js +16 -0
  9. package/dist/agent/schema-registry.js.map +1 -0
  10. package/dist/config.d.ts +49 -25
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +13 -7
  13. package/dist/config.js.map +1 -1
  14. package/dist/create-core.d.ts +12 -0
  15. package/dist/create-core.d.ts.map +1 -0
  16. package/dist/create-core.js +49 -0
  17. package/dist/create-core.js.map +1 -0
  18. package/dist/index.cjs +390 -134
  19. package/dist/index.cjs.map +14 -9
  20. package/dist/index.d.ts +7 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +391 -135
  23. package/dist/index.js.map +14 -9
  24. package/dist/queue/actions.d.ts +35 -0
  25. package/dist/queue/actions.d.ts.map +1 -0
  26. package/dist/queue/actions.js +2 -0
  27. package/dist/queue/actions.js.map +1 -0
  28. package/dist/queue/base.d.ts +7 -0
  29. package/dist/queue/base.d.ts.map +1 -0
  30. package/dist/queue/base.js +2 -0
  31. package/dist/queue/base.js.map +1 -0
  32. package/dist/queue/in-memory.d.ts +9 -0
  33. package/dist/queue/in-memory.d.ts.map +1 -0
  34. package/dist/queue/in-memory.js +18 -0
  35. package/dist/queue/in-memory.js.map +1 -0
  36. package/dist/tracing/context.d.ts +12 -0
  37. package/dist/tracing/context.d.ts.map +1 -1
  38. package/dist/tracing/context.js +41 -5
  39. package/dist/tracing/context.js.map +1 -1
  40. package/dist/tracing/span.d.ts +5 -0
  41. package/dist/tracing/span.d.ts.map +1 -1
  42. package/dist/tracing/span.js +29 -0
  43. package/dist/tracing/span.js.map +1 -1
  44. package/dist/tracing/tracer.d.ts +7 -17
  45. package/dist/tracing/tracer.d.ts.map +1 -1
  46. package/dist/tracing/tracer.js +20 -39
  47. package/dist/tracing/tracer.js.map +1 -1
  48. package/dist/transport/base.d.ts +2 -22
  49. package/dist/transport/base.d.ts.map +1 -1
  50. package/dist/transport/http.d.ts +5 -28
  51. package/dist/transport/http.d.ts.map +1 -1
  52. package/dist/transport/http.js +76 -110
  53. package/dist/transport/http.js.map +1 -1
  54. package/dist/transport/stdio.d.ts +4 -16
  55. package/dist/transport/stdio.d.ts.map +1 -1
  56. package/dist/transport/stdio.js +14 -29
  57. package/dist/transport/stdio.js.map +1 -1
  58. package/dist/transport/worker.d.ts +22 -0
  59. package/dist/transport/worker.d.ts.map +1 -0
  60. package/dist/transport/worker.js +85 -0
  61. package/dist/transport/worker.js.map +1 -0
  62. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,14 +1,101 @@
1
+ // packages/core/src/agent/instance-manager.ts
2
+ import { isDeepStrictEqual } from "node:util";
3
+
4
+ // packages/core/src/agent/schema-registry.ts
5
+ class SchemaRegistry {
6
+ schemas = new Map;
7
+ register(schema) {
8
+ this.schemas.set(this.getKey(schema.schemaName, schema.schemaIdentifier), schema);
9
+ }
10
+ has(schemaName, schemaIdentifier) {
11
+ return this.schemas.has(this.getKey(schemaName, schemaIdentifier));
12
+ }
13
+ get(schemaName, schemaIdentifier) {
14
+ return this.schemas.get(this.getKey(schemaName, schemaIdentifier));
15
+ }
16
+ getKey(schemaName, schemaIdentifier) {
17
+ return `${schemaName}@${schemaIdentifier}`;
18
+ }
19
+ }
20
+
21
+ // packages/core/src/agent/instance-manager.ts
22
+ var orderInsensitiveArrayKeys = new Set(["required", "enum", "oneOf", "allOf", "anyOf", "type"]);
23
+ var stableStringify = (value) => JSON.stringify(value) ?? "undefined";
24
+ var normalizeSchema = (value, arrayKey) => {
25
+ if (Array.isArray(value)) {
26
+ const normalizedItems = value.map((item) => normalizeSchema(item));
27
+ if (arrayKey && orderInsensitiveArrayKeys.has(arrayKey)) {
28
+ return normalizedItems.map((item) => ({ item, key: stableStringify(item) })).sort((left, right) => left.key.localeCompare(right.key)).map(({ item }) => item);
29
+ }
30
+ return normalizedItems;
31
+ }
32
+ if (value && typeof value === "object") {
33
+ const proto = Object.getPrototypeOf(value);
34
+ if (proto === Object.prototype || proto === null) {
35
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
36
+ const normalized = {};
37
+ for (const [key, entryValue] of entries) {
38
+ normalized[key] = normalizeSchema(entryValue, key);
39
+ }
40
+ return normalized;
41
+ }
42
+ }
43
+ return value;
44
+ };
45
+
46
+ class AgentInstanceManager {
47
+ queue;
48
+ options;
49
+ schemaRegistry = new SchemaRegistry;
50
+ constructor(queue, options) {
51
+ this.queue = queue;
52
+ this.options = options;
53
+ }
54
+ registerSchema(schema) {
55
+ if (this.schemaRegistry.has(this.options.schemaName, this.options.schemaIdentifier)) {
56
+ const existing = this.schemaRegistry.get(this.options.schemaName, this.options.schemaIdentifier);
57
+ if (existing && !isDeepStrictEqual(normalizeSchema(existing.schema), normalizeSchema(schema))) {
58
+ console.warn(`Schema ${this.options.schemaName}@${this.options.schemaIdentifier} is already registered with a different payload. Ignoring registration.`);
59
+ }
60
+ return;
61
+ }
62
+ const registration = {
63
+ schemaName: this.options.schemaName,
64
+ schemaIdentifier: this.options.schemaIdentifier,
65
+ schema
66
+ };
67
+ this.schemaRegistry.register(registration);
68
+ this.queue.enqueue({ type: "schema_register", data: registration });
69
+ }
70
+ startInstance(options = {}) {
71
+ if (!this.options.allowUnregisteredSchema && !this.schemaRegistry.has(this.options.schemaName, this.options.schemaIdentifier)) {
72
+ console.warn(`Schema ${this.options.schemaName}@${this.options.schemaIdentifier} must be registered before starting an agent instance.`);
73
+ return;
74
+ }
75
+ const startData = {
76
+ ...options,
77
+ schemaName: this.options.schemaName,
78
+ schemaIdentifier: this.options.schemaIdentifier
79
+ };
80
+ this.queue.enqueue({ type: "agent_start", data: startData });
81
+ }
82
+ finishInstance() {
83
+ this.queue.enqueue({ type: "agent_finish", data: {} });
84
+ }
85
+ }
1
86
  // packages/core/src/config.ts
2
87
  import { z } from "zod";
3
88
  var HttpTransportConfigSchema = z.object({
4
89
  apiUrl: z.string().url(),
5
90
  apiToken: z.string().min(1),
6
91
  agentId: z.string().optional(),
7
- agentVersion: z.string().optional(),
92
+ agentIdentifier: z.string().default("v1.0.0"),
8
93
  agentName: z.string().optional(),
9
94
  agentDescription: z.string().optional(),
95
+ schemaName: z.string().optional(),
96
+ schemaIdentifier: z.string().optional(),
10
97
  agentSchema: z.record(z.unknown()).optional(),
11
- agentSchemaVersion: z.string().optional(),
98
+ agentSchemaIdentifier: z.string().optional(),
12
99
  skipSchema: z.boolean().default(false),
13
100
  requestTimeout: z.number().positive().default(30000),
14
101
  connectTimeout: z.number().positive().default(1e4),
@@ -21,11 +108,13 @@ var PartialHttpConfigSchema = z.object({
21
108
  apiUrl: z.string().url(),
22
109
  apiToken: z.string().min(1),
23
110
  agentId: z.string().optional(),
24
- agentVersion: z.string().optional(),
111
+ agentIdentifier: z.string().optional(),
25
112
  agentName: z.string().optional(),
26
113
  agentDescription: z.string().optional(),
114
+ schemaName: z.string().optional(),
115
+ schemaIdentifier: z.string().optional(),
27
116
  agentSchema: z.record(z.unknown()).optional(),
28
- agentSchemaVersion: z.string().optional(),
117
+ agentSchemaIdentifier: z.string().optional(),
29
118
  skipSchema: z.boolean().optional(),
30
119
  requestTimeout: z.number().positive().optional(),
31
120
  connectTimeout: z.number().positive().optional(),
@@ -55,24 +144,75 @@ function createConfig(options) {
55
144
  };
56
145
  return ConfigSchema.parse(config);
57
146
  }
147
+ // packages/core/src/create-core.ts
148
+ import { extractPartition } from "@prefactor/pfid";
149
+
150
+ // packages/core/src/queue/in-memory.ts
151
+ class InMemoryQueue {
152
+ items = [];
153
+ enqueue(item) {
154
+ this.items.push(item);
155
+ }
156
+ dequeueBatch(maxItems) {
157
+ if (this.items.length === 0)
158
+ return [];
159
+ return this.items.splice(0, maxItems);
160
+ }
161
+ size() {
162
+ return this.items.length;
163
+ }
164
+ async flush() {
165
+ return;
166
+ }
167
+ }
168
+
169
+ // packages/core/src/tracing/tracer.ts
170
+ import { generate, generatePartition } from "@prefactor/pfid";
171
+
58
172
  // packages/core/src/tracing/context.ts
59
- import { AsyncLocalStorage } from "node:async_hooks";
173
+ import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
60
174
  var spanStorage = new AsyncLocalStorage;
61
175
 
62
176
  class SpanContext {
63
177
  static getCurrent() {
64
- return spanStorage.getStore();
178
+ const stack = spanStorage.getStore() ?? [];
179
+ return stack[stack.length - 1];
180
+ }
181
+ static getStack() {
182
+ return [...spanStorage.getStore() ?? []];
183
+ }
184
+ static enter(span) {
185
+ const stack = [...spanStorage.getStore() ?? [], span];
186
+ spanStorage.enterWith(stack);
187
+ }
188
+ static exit() {
189
+ const stack = [...spanStorage.getStore() ?? []];
190
+ stack.pop();
191
+ spanStorage.enterWith(stack);
65
192
  }
66
193
  static run(span, fn) {
67
- return spanStorage.run(span, fn);
194
+ const stack = spanStorage.getStore() ?? [];
195
+ const resource = new AsyncResource("SpanContext.run");
196
+ try {
197
+ return resource.runInAsyncScope(() => spanStorage.run([...stack, span], fn));
198
+ } finally {
199
+ resource.emitDestroy();
200
+ }
68
201
  }
69
202
  static async runAsync(span, fn) {
70
- return spanStorage.run(span, fn);
203
+ const stack = spanStorage.getStore() ?? [];
204
+ const resource = new AsyncResource("SpanContext.runAsync");
205
+ try {
206
+ return await resource.runInAsyncScope(() => spanStorage.run([...stack, span], fn));
207
+ } finally {
208
+ resource.emitDestroy();
209
+ }
71
210
  }
72
211
  static clear() {
73
212
  spanStorage.disable();
74
213
  }
75
214
  }
215
+
76
216
  // packages/core/src/tracing/span.ts
77
217
  var SpanType;
78
218
  ((SpanType2) => {
@@ -82,27 +222,53 @@ var SpanType;
82
222
  SpanType2["CHAIN"] = "chain";
83
223
  SpanType2["RETRIEVER"] = "retriever";
84
224
  })(SpanType ||= {});
225
+ var DEFAULT_AGENT_SCHEMA = {
226
+ external_identifier: "1.0.0",
227
+ span_schemas: {
228
+ agent: {
229
+ type: "object",
230
+ properties: { type: { type: "string", const: "agent" } }
231
+ },
232
+ llm: {
233
+ type: "object",
234
+ properties: { type: { type: "string", const: "llm" } }
235
+ },
236
+ tool: {
237
+ type: "object",
238
+ properties: { type: { type: "string", const: "tool" } }
239
+ },
240
+ chain: {
241
+ type: "object",
242
+ properties: { type: { type: "string", const: "chain" } }
243
+ },
244
+ retriever: {
245
+ type: "object",
246
+ properties: { type: { type: "string", const: "retriever" } }
247
+ }
248
+ }
249
+ };
85
250
  var SpanStatus;
86
251
  ((SpanStatus2) => {
87
252
  SpanStatus2["RUNNING"] = "running";
88
253
  SpanStatus2["SUCCESS"] = "success";
89
254
  SpanStatus2["ERROR"] = "error";
90
255
  })(SpanStatus ||= {});
256
+
91
257
  // packages/core/src/tracing/tracer.ts
92
- import { generate, generatePartition } from "@prefactor/pfid";
93
258
  class Tracer {
94
- transport;
259
+ queue;
95
260
  partition;
96
- constructor(transport, partition) {
97
- this.transport = transport;
261
+ constructor(queue, partition) {
262
+ this.queue = queue;
98
263
  this.partition = partition ?? generatePartition();
99
264
  }
100
265
  startSpan(options) {
266
+ const parentSpan = SpanContext.getCurrent();
101
267
  const spanId = generate(this.partition);
102
- const traceId = options.traceId ?? generate(this.partition);
268
+ const traceId = parentSpan?.traceId ?? generate(this.partition);
103
269
  const span = {
104
270
  spanId,
105
- parentSpanId: options.parentSpanId ?? null,
271
+ parentSpanId: parentSpan?.spanId ?? null,
106
272
  traceId,
107
273
  name: options.name,
108
274
  spanType: options.spanType,
@@ -116,17 +282,18 @@ class Tracer {
116
282
  metadata: options.metadata ?? {},
117
283
  tags: options.tags ?? []
118
284
  };
119
- if (options.spanType === "agent") {
285
+ if (options.spanType === "agent" /* AGENT */) {
120
286
  try {
121
- this.transport.emit(span);
287
+ this.queue.enqueue({ type: "span_end", data: span });
122
288
  } catch (error) {
123
- console.error("Failed to emit agent span:", error);
289
+ console.error("Failed to enqueue agent span:", error);
124
290
  }
125
291
  }
126
292
  return span;
127
293
  }
128
294
  endSpan(span, options) {
129
- span.endTime = Date.now();
295
+ const endTime = Date.now();
296
+ span.endTime = endTime;
130
297
  span.outputs = options?.outputs ?? null;
131
298
  span.tokenUsage = options?.tokenUsage ?? null;
132
299
  if (options?.error) {
@@ -140,37 +307,24 @@ class Tracer {
140
307
  span.status = "success" /* SUCCESS */;
141
308
  }
142
309
  try {
143
- if (span.spanType === "agent") {
144
- this.transport.finishSpan(span.spanId, span.endTime);
310
+ if (span.spanType === "agent" /* AGENT */) {
311
+ this.queue.enqueue({ type: "span_finish", data: { spanId: span.spanId, endTime } });
145
312
  } else {
146
- this.transport.emit(span);
313
+ this.queue.enqueue({ type: "span_end", data: span });
147
314
  }
148
315
  } catch (error) {
149
- console.error("Failed to emit/finish span:", error);
150
- }
151
- }
152
- startAgentInstance() {
153
- try {
154
- this.transport.startAgentInstance();
155
- } catch (error) {
156
- console.error("Failed to start agent instance:", error);
157
- }
158
- }
159
- finishAgentInstance() {
160
- try {
161
- this.transport.finishAgentInstance();
162
- } catch (error) {
163
- console.error("Failed to finish agent instance:", error);
316
+ console.error("Failed to enqueue span action:", error);
164
317
  }
165
318
  }
166
319
  async close() {
167
320
  try {
168
- await this.transport.close();
321
+ await this.queue.flush();
169
322
  } catch (error) {
170
- console.error("Failed to close transport:", error);
323
+ console.error("Failed to flush queue:", error);
171
324
  }
172
325
  }
173
326
  }
327
+
174
328
  // packages/core/src/utils/logging.ts
175
329
  class Logger {
176
330
  namespace;
@@ -223,73 +377,76 @@ var logger = getLogger("http-transport");
223
377
 
224
378
  class HttpTransport {
225
379
  config;
226
- queue = [];
227
- processing = false;
228
380
  closed = false;
229
381
  agentInstanceId = null;
230
382
  spanIdMap = new Map;
383
+ pendingFinishes = new Map;
231
384
  constructor(config) {
232
385
  this.config = config;
233
- this.startProcessing();
234
- }
235
- emit(span) {
236
- if (this.closed) {
237
- return;
238
- }
239
- this.queue.push({ type: "span", data: span });
240
- }
241
- finishSpan(spanId, endTime) {
242
- if (this.closed) {
243
- return;
244
- }
245
- const timestamp = new Date(endTime).toISOString();
246
- this.queue.push({ type: "finish_span", data: { spanId, timestamp } });
247
- }
248
- startAgentInstance() {
249
- if (this.closed) {
250
- return;
251
- }
252
- this.queue.push({ type: "start_agent", data: null });
253
386
  }
254
- finishAgentInstance() {
255
- if (this.closed) {
387
+ async processBatch(items) {
388
+ if (this.closed || items.length === 0) {
256
389
  return;
257
390
  }
258
- this.queue.push({ type: "finish_agent", data: null });
259
- }
260
- async startProcessing() {
261
- this.processing = true;
262
- while (!this.closed || this.queue.length > 0) {
263
- if (this.queue.length === 0) {
264
- await new Promise((resolve) => setTimeout(resolve, 100));
265
- continue;
391
+ for (const item of items) {
392
+ if (item.type === "schema_register") {
393
+ this.config.agentSchema = item.data.schema;
394
+ this.config.agentSchemaIdentifier = item.data.schemaIdentifier;
395
+ this.config.schemaName = item.data.schemaName;
396
+ this.config.schemaIdentifier = item.data.schemaIdentifier;
266
397
  }
267
- const item = this.queue.shift();
268
- if (!item)
269
- continue;
398
+ }
399
+ for (const item of items) {
270
400
  try {
271
- if (!this.agentInstanceId && item.type !== "start_agent") {
272
- await this.ensureAgentRegistered();
273
- }
274
401
  switch (item.type) {
275
- case "span":
276
- await this.sendSpan(item.data);
277
- break;
278
- case "finish_span":
279
- await this.finishSpanHttp(item.data);
402
+ case "schema_register":
280
403
  break;
281
- case "start_agent":
404
+ case "agent_start":
405
+ this.config.agentId = item.data.agentId;
406
+ if (item.data.agentIdentifier !== undefined)
407
+ this.config.agentIdentifier = item.data.agentIdentifier;
408
+ this.config.agentName = item.data.agentName;
409
+ this.config.agentDescription = item.data.agentDescription;
282
410
  await this.startAgentInstanceHttp();
283
411
  break;
284
- case "finish_agent":
412
+ case "agent_finish":
285
413
  await this.finishAgentInstanceHttp();
286
414
  break;
415
+ case "span_end":
416
+ if (!this.agentInstanceId) {
417
+ await this.ensureAgentRegistered();
418
+ }
419
+ await this.sendSpan(item.data);
420
+ break;
421
+ case "span_finish": {
422
+ const spanId = item.data.spanId;
423
+ const backendSpanId = this.spanIdMap.get(spanId);
424
+ if (backendSpanId) {
425
+ const timestamp = new Date(item.data.endTime).toISOString();
426
+ await this.finishSpanHttp({ spanId, timestamp });
427
+ } else {
428
+ this.pendingFinishes.set(spanId, item.data.endTime);
429
+ }
430
+ break;
431
+ }
287
432
  }
288
433
  } catch (error) {
289
- logger.error("Error processing queue item:", error);
434
+ logger.error("Error processing batch item:", error);
290
435
  }
291
436
  }
292
- this.processing = false;
437
+ }
438
+ async processPendingFinishes(spanId) {
439
+ const pendingEndTime = this.pendingFinishes.get(spanId);
440
+ if (!pendingEndTime) {
441
+ return;
442
+ }
443
+ try {
444
+ const timestamp = new Date(pendingEndTime).toISOString();
445
+ await this.finishSpanHttp({ spanId, timestamp });
446
+ this.pendingFinishes.delete(spanId);
447
+ } catch (error) {
448
+ logger.error("Error processing pending span finish:", error);
449
+ }
293
450
  }
294
451
  async sendSpan(span, retry = 0) {
295
452
  const url = `${this.config.apiUrl}/api/v1/agent_spans`;
@@ -309,6 +466,7 @@ class HttpTransport {
309
466
  const backendSpanId = data?.details?.id;
310
467
  if (backendSpanId) {
311
468
  this.spanIdMap.set(span.spanId, backendSpanId);
469
+ await this.processPendingFinishes(span.spanId);
312
470
  }
313
471
  return;
314
472
  }
@@ -370,31 +528,7 @@ class HttpTransport {
370
528
  };
371
529
  }
372
530
  getDefaultSchema() {
373
- return {
374
- external_identifier: "1.0.0",
375
- span_schemas: {
376
- agent: {
377
- type: "object",
378
- properties: { type: { type: "string", const: "agent" } }
379
- },
380
- llm: {
381
- type: "object",
382
- properties: { type: { type: "string", const: "llm" } }
383
- },
384
- tool: {
385
- type: "object",
386
- properties: { type: { type: "string", const: "tool" } }
387
- },
388
- chain: {
389
- type: "object",
390
- properties: { type: { type: "string", const: "chain" } }
391
- },
392
- retriever: {
393
- type: "object",
394
- properties: { type: { type: "string", const: "retriever" } }
395
- }
396
- }
397
- };
531
+ return DEFAULT_AGENT_SCHEMA;
398
532
  }
399
533
  async ensureAgentRegistered() {
400
534
  if (this.agentInstanceId) {
@@ -404,9 +538,9 @@ class HttpTransport {
404
538
  const payload = {};
405
539
  if (this.config.agentId)
406
540
  payload.agent_id = this.config.agentId;
407
- if (this.config.agentVersion) {
541
+ if (this.config.agentIdentifier) {
408
542
  payload.agent_version = {
409
- external_identifier: this.config.agentVersion,
543
+ external_identifier: this.config.agentIdentifier,
410
544
  name: this.config.agentName || "Agent",
411
545
  description: this.config.agentDescription || ""
412
546
  };
@@ -416,10 +550,10 @@ class HttpTransport {
416
550
  } else if (this.config.agentSchema) {
417
551
  logger.debug("Using custom agent schema");
418
552
  payload.agent_schema_version = this.config.agentSchema;
419
- } else if (this.config.agentSchemaVersion) {
420
- logger.debug(`Using schema version: ${this.config.agentSchemaVersion}`);
553
+ } else if (this.config.agentSchemaIdentifier) {
554
+ logger.debug(`Using schema version: ${this.config.agentSchemaIdentifier}`);
421
555
  payload.agent_schema_version = {
422
- external_identifier: this.config.agentSchemaVersion
556
+ external_identifier: this.config.agentSchemaIdentifier
423
557
  };
424
558
  } else {
425
559
  logger.debug("Using default hardcoded schema (v1.0.0)");
@@ -492,6 +626,7 @@ class HttpTransport {
492
626
  } catch (error) {
493
627
  logger.error("Error finishing agent instance:", error);
494
628
  }
629
+ this.agentInstanceId = null;
495
630
  }
496
631
  async finishSpanHttp(data) {
497
632
  const backendSpanId = this.spanIdMap.get(data.spanId);
@@ -519,16 +654,13 @@ class HttpTransport {
519
654
  }
520
655
  async close() {
521
656
  this.closed = true;
522
- const timeout = 1e4;
523
- const start = Date.now();
524
- while (this.processing && Date.now() - start < timeout) {
525
- await new Promise((resolve) => setTimeout(resolve, 100));
526
- }
527
- if (this.processing) {
528
- logger.warn("Transport closed with pending queue items");
657
+ if (this.pendingFinishes.size > 0) {
658
+ logger.warn(`Transport closed with ${this.pendingFinishes.size} pending span finish(es) that could not be processed`);
659
+ this.pendingFinishes.clear();
529
660
  }
530
661
  }
531
662
  }
663
+
532
664
  // packages/core/src/utils/serialization.ts
533
665
  function truncateString(value, maxLength) {
534
666
  if (value.length <= maxLength) {
@@ -567,33 +699,153 @@ function serializeValue(value, maxLength = 1e4) {
567
699
  class StdioTransport {
568
700
  closed = false;
569
701
  writeLock = Promise.resolve();
570
- emit(span) {
571
- if (this.closed) {
702
+ async processBatch(items) {
703
+ if (this.closed || items.length === 0) {
572
704
  return;
573
705
  }
574
706
  this.writeLock = this.writeLock.then(async () => {
575
- try {
576
- const serialized = serializeValue(span);
577
- const json = JSON.stringify(serialized);
578
- await Bun.write(Bun.stdout, `${json}
707
+ for (const item of items) {
708
+ try {
709
+ const serialized = serializeValue(item);
710
+ const json = JSON.stringify(serialized);
711
+ await Bun.write(Bun.stdout, `${json}
579
712
  `);
580
- } catch (error) {
581
- console.error("Failed to emit span to stdout:", error);
713
+ } catch (error) {
714
+ console.error("Failed to emit queue action to stdout:", error);
715
+ }
582
716
  }
583
717
  });
718
+ await this.writeLock;
584
719
  }
585
- finishSpan() {}
586
- startAgentInstance() {}
587
- finishAgentInstance() {}
588
720
  async close() {
589
721
  this.closed = true;
590
722
  await this.writeLock;
591
723
  }
592
724
  }
725
+
726
+ // packages/core/src/transport/worker.ts
727
+ class TransportWorker {
728
+ queue;
729
+ transport;
730
+ config;
731
+ closed = false;
732
+ inFlightPromise = null;
733
+ loopPromise;
734
+ pendingBatch = null;
735
+ constructor(queue, transport, config) {
736
+ this.queue = queue;
737
+ this.transport = transport;
738
+ this.config = config;
739
+ this.loopPromise = this.start();
740
+ }
741
+ async start() {
742
+ while (!this.closed || this.pendingBatch || this.queue.size() > 0 || this.inFlightPromise) {
743
+ const batch = this.pendingBatch ?? this.queue.dequeueBatch(this.config.batchSize);
744
+ if (batch.length === 0) {
745
+ await new Promise((resolve) => setTimeout(resolve, this.config.intervalMs));
746
+ continue;
747
+ }
748
+ try {
749
+ const inFlight = this.transport.processBatch(batch);
750
+ this.inFlightPromise = inFlight;
751
+ await inFlight;
752
+ this.pendingBatch = null;
753
+ } catch (error) {
754
+ this.pendingBatch = batch;
755
+ console.error("TransportWorker.processBatch failed", error);
756
+ await new Promise((resolve) => setTimeout(resolve, this.config.intervalMs));
757
+ } finally {
758
+ this.inFlightPromise = null;
759
+ }
760
+ }
761
+ }
762
+ async flush(timeoutMs) {
763
+ const start = Date.now();
764
+ while ((this.queue.size() > 0 || this.pendingBatch || this.inFlightPromise) && Date.now() - start < timeoutMs) {
765
+ await new Promise((resolve) => setTimeout(resolve, this.config.intervalMs));
766
+ }
767
+ }
768
+ async close(timeoutMs = this.config.intervalMs * 50) {
769
+ this.closed = true;
770
+ const deadline = Date.now() + timeoutMs;
771
+ const awaitWithTimeout = async (promise, label, timeoutMs2, warnOnTimeout = true) => {
772
+ if (timeoutMs2 <= 0) {
773
+ if (warnOnTimeout) {
774
+ console.warn(`TransportWorker.close timed out waiting for ${label}`);
775
+ }
776
+ return false;
777
+ }
778
+ const resolvedPromise = Promise.resolve(promise);
779
+ let timeoutId = null;
780
+ const timeoutPromise = new Promise((resolve) => {
781
+ timeoutId = setTimeout(() => resolve(false), timeoutMs2);
782
+ });
783
+ const completed = await Promise.race([resolvedPromise.then(() => true), timeoutPromise]);
784
+ if (timeoutId) {
785
+ clearTimeout(timeoutId);
786
+ }
787
+ if (!completed && warnOnTimeout) {
788
+ console.warn(`TransportWorker.close timed out waiting for ${label}`);
789
+ }
790
+ return completed;
791
+ };
792
+ const remainingTime = () => Math.max(0, deadline - Date.now());
793
+ const loopCompleted = await awaitWithTimeout(this.loopPromise, "loop to finish", remainingTime(), false);
794
+ if (!loopCompleted) {
795
+ console.warn("TransportWorker.close timed out waiting for loop to finish; closing transport now may cause potential data loss");
796
+ }
797
+ const safeTransportClose = async () => {
798
+ try {
799
+ await this.transport.close();
800
+ } catch (error) {
801
+ console.error("TransportWorker.close failed", error);
802
+ }
803
+ };
804
+ await awaitWithTimeout(safeTransportClose(), "transport to close", remainingTime());
805
+ }
806
+ }
807
+
808
+ // packages/core/src/create-core.ts
809
+ function createCore(config) {
810
+ let transport;
811
+ if (config.transportType === "stdio") {
812
+ transport = new StdioTransport;
813
+ } else {
814
+ if (!config.httpConfig) {
815
+ throw new Error("HTTP transport requires httpConfig to be provided in configuration");
816
+ }
817
+ const httpConfig = HttpTransportConfigSchema.parse(config.httpConfig);
818
+ transport = new HttpTransport(httpConfig);
819
+ }
820
+ let partition;
821
+ if (config.httpConfig?.agentId) {
822
+ try {
823
+ partition = extractPartition(config.httpConfig.agentId);
824
+ } catch {
825
+ partition = undefined;
826
+ }
827
+ }
828
+ const queue = new InMemoryQueue;
829
+ const worker = new TransportWorker(queue, transport, { batchSize: 25, intervalMs: 50 });
830
+ const tracer = new Tracer(queue, partition);
831
+ const schemaName = config.httpConfig?.schemaName ?? "prefactor:agent";
832
+ const schemaIdentifier = config.httpConfig?.schemaIdentifier ?? "1.0.0";
833
+ const allowUnregisteredSchema = config.transportType === "http" && Boolean(config.httpConfig?.skipSchema || config.httpConfig?.agentSchema || config.httpConfig?.agentSchemaIdentifier);
834
+ const agentManager = new AgentInstanceManager(queue, {
835
+ schemaName,
836
+ schemaIdentifier,
837
+ allowUnregisteredSchema
838
+ });
839
+ const shutdown = async () => {
840
+ await worker.close();
841
+ };
842
+ return { tracer, agentManager, worker, shutdown };
843
+ }
593
844
  export {
594
845
  truncateString,
595
846
  serializeValue,
596
847
  getLogger,
848
+ createCore,
597
849
  createConfig,
598
850
  configureLogging,
599
851
  Tracer,
@@ -601,10 +853,14 @@ export {
601
853
  SpanType,
602
854
  SpanStatus,
603
855
  SpanContext,
856
+ SchemaRegistry,
604
857
  PartialHttpConfigSchema,
858
+ InMemoryQueue,
605
859
  HttpTransportConfigSchema,
606
860
  HttpTransport,
607
- ConfigSchema
861
+ DEFAULT_AGENT_SCHEMA,
862
+ ConfigSchema,
863
+ AgentInstanceManager
608
864
  };
609
865
 
610
- //# debugId=48E67D6DE4160D4864756E2164756E21
866
+ //# debugId=AE4689D591D1DB9C64756E2164756E21