@prefactor/core 0.2.0 → 0.2.2

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 (94) hide show
  1. package/dist/agent/instance-manager.d.ts +5 -8
  2. package/dist/agent/instance-manager.d.ts.map +1 -1
  3. package/dist/agent/instance-manager.js +34 -55
  4. package/dist/agent/instance-manager.js.map +1 -1
  5. package/dist/config.d.ts +15 -63
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +29 -20
  8. package/dist/config.js.map +1 -1
  9. package/dist/create-core.d.ts +0 -2
  10. package/dist/create-core.d.ts.map +1 -1
  11. package/dist/create-core.js +17 -28
  12. package/dist/create-core.js.map +1 -1
  13. package/dist/index.cjs +678 -453
  14. package/dist/index.cjs.map +20 -15
  15. package/dist/index.d.ts +4 -7
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +678 -453
  18. package/dist/index.js.map +20 -15
  19. package/dist/lifecycle.d.ts +5 -0
  20. package/dist/lifecycle.d.ts.map +1 -0
  21. package/dist/lifecycle.js +27 -0
  22. package/dist/lifecycle.js.map +1 -0
  23. package/dist/queue/actions.d.ts +15 -26
  24. package/dist/queue/actions.d.ts.map +1 -1
  25. package/dist/queue/base.d.ts +15 -3
  26. package/dist/queue/base.d.ts.map +1 -1
  27. package/dist/queue/in-memory-queue.d.ts +11 -0
  28. package/dist/queue/in-memory-queue.d.ts.map +1 -0
  29. package/dist/queue/in-memory-queue.js +46 -0
  30. package/dist/queue/in-memory-queue.js.map +1 -0
  31. package/dist/queue/task-executor.d.ts +18 -0
  32. package/dist/queue/task-executor.d.ts.map +1 -0
  33. package/dist/queue/task-executor.js +77 -0
  34. package/dist/queue/task-executor.js.map +1 -0
  35. package/dist/tracing/active-tracer.d.ts +5 -0
  36. package/dist/tracing/active-tracer.d.ts.map +1 -0
  37. package/dist/tracing/active-tracer.js +13 -0
  38. package/dist/tracing/active-tracer.js.map +1 -0
  39. package/dist/tracing/span.d.ts +7 -14
  40. package/dist/tracing/span.d.ts.map +1 -1
  41. package/dist/tracing/span.js +5 -36
  42. package/dist/tracing/span.js.map +1 -1
  43. package/dist/tracing/tracer.d.ts +6 -7
  44. package/dist/tracing/tracer.d.ts.map +1 -1
  45. package/dist/tracing/tracer.js +27 -12
  46. package/dist/tracing/tracer.js.map +1 -1
  47. package/dist/tracing/with-span.d.ts +4 -0
  48. package/dist/tracing/with-span.d.ts.map +1 -0
  49. package/dist/tracing/with-span.js +35 -0
  50. package/dist/tracing/with-span.js.map +1 -0
  51. package/dist/transport/http/agent-instance-client.d.ts +23 -0
  52. package/dist/transport/http/agent-instance-client.d.ts.map +1 -0
  53. package/dist/transport/http/agent-instance-client.js +25 -0
  54. package/dist/transport/http/agent-instance-client.js.map +1 -0
  55. package/dist/transport/http/agent-span-client.d.ts +25 -0
  56. package/dist/transport/http/agent-span-client.d.ts.map +1 -0
  57. package/dist/transport/http/agent-span-client.js +37 -0
  58. package/dist/transport/http/agent-span-client.js.map +1 -0
  59. package/dist/transport/http/http-client.d.ts +43 -0
  60. package/dist/transport/http/http-client.d.ts.map +1 -0
  61. package/dist/transport/http/http-client.js +127 -0
  62. package/dist/transport/http/http-client.js.map +1 -0
  63. package/dist/transport/http/retry-policy.d.ts +4 -0
  64. package/dist/transport/http/retry-policy.d.ts.map +1 -0
  65. package/dist/transport/http/retry-policy.js +10 -0
  66. package/dist/transport/http/retry-policy.js.map +1 -0
  67. package/dist/transport/http.d.ts +34 -50
  68. package/dist/transport/http.d.ts.map +1 -1
  69. package/dist/transport/http.js +163 -227
  70. package/dist/transport/http.js.map +1 -1
  71. package/dist/utils/logging.d.ts.map +1 -1
  72. package/dist/utils/logging.js +7 -1
  73. package/dist/utils/logging.js.map +1 -1
  74. package/package.json +1 -1
  75. package/dist/agent/schema-registry.d.ts +0 -9
  76. package/dist/agent/schema-registry.d.ts.map +0 -1
  77. package/dist/agent/schema-registry.js +0 -16
  78. package/dist/agent/schema-registry.js.map +0 -1
  79. package/dist/queue/in-memory.d.ts +0 -9
  80. package/dist/queue/in-memory.d.ts.map +0 -1
  81. package/dist/queue/in-memory.js +0 -18
  82. package/dist/queue/in-memory.js.map +0 -1
  83. package/dist/transport/base.d.ts +0 -18
  84. package/dist/transport/base.d.ts.map +0 -1
  85. package/dist/transport/base.js +0 -2
  86. package/dist/transport/base.js.map +0 -1
  87. package/dist/transport/stdio.d.ts +0 -36
  88. package/dist/transport/stdio.d.ts.map +0 -1
  89. package/dist/transport/stdio.js +0 -56
  90. package/dist/transport/stdio.js.map +0 -1
  91. package/dist/transport/worker.d.ts +0 -22
  92. package/dist/transport/worker.d.ts.map +0 -1
  93. package/dist/transport/worker.js +0 -85
  94. package/dist/transport/worker.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,90 +1,60 @@
1
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
2
  class AgentInstanceManager {
47
- queue;
3
+ transport;
48
4
  options;
49
- schemaRegistry = new SchemaRegistry;
50
- constructor(queue, options) {
51
- this.queue = queue;
5
+ registeredSchema = null;
6
+ constructor(transport, options) {
7
+ this.transport = transport;
52
8
  this.options = options;
53
9
  }
54
10
  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
- }
11
+ if (this.registeredSchema === null) {
12
+ this.registeredSchema = schema;
13
+ this.transport.registerSchema(schema);
60
14
  return;
61
15
  }
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 });
16
+ const existingSchema = stableStringify(this.registeredSchema);
17
+ const incomingSchema = stableStringify(schema);
18
+ if (existingSchema !== incomingSchema) {
19
+ console.warn("A different schema was provided after registration; ignoring subsequent schema.");
20
+ }
69
21
  }
70
22
  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.`);
23
+ if (!this.options.allowUnregisteredSchema && this.registeredSchema === null) {
24
+ console.warn("Schema must be registered before starting an agent instance.");
73
25
  return;
74
26
  }
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 });
27
+ this.transport.startAgentInstance(options);
81
28
  }
82
29
  finishInstance() {
83
- this.queue.enqueue({ type: "agent_finish", data: {} });
30
+ this.transport.finishAgentInstance();
84
31
  }
85
32
  }
33
+ function stableStringify(value) {
34
+ return JSON.stringify(normalizeValue(value));
35
+ }
36
+ function normalizeValue(value) {
37
+ if (Array.isArray(value)) {
38
+ return value.map((entry) => normalizeValue(entry));
39
+ }
40
+ if (value && typeof value === "object") {
41
+ const normalized = {};
42
+ const objectValue = value;
43
+ const keys = Object.keys(objectValue).sort((a, b) => a.localeCompare(b));
44
+ for (const key of keys) {
45
+ normalized[key] = normalizeValue(objectValue[key]);
46
+ }
47
+ return normalized;
48
+ }
49
+ return value;
50
+ }
86
51
  // packages/core/src/config.ts
87
52
  import { z } from "zod";
53
+ var DEFAULT_RETRY_ON_STATUS_CODES = [
54
+ 429,
55
+ ...Array.from({ length: 100 }, (_, index) => 500 + index)
56
+ ];
57
+ var HttpStatusCodeSchema = z.number().int().min(100).max(599);
88
58
  var HttpTransportConfigSchema = z.object({
89
59
  apiUrl: z.string().url(),
90
60
  apiToken: z.string().min(1),
@@ -92,17 +62,13 @@ var HttpTransportConfigSchema = z.object({
92
62
  agentIdentifier: z.string().default("v1.0.0"),
93
63
  agentName: z.string().optional(),
94
64
  agentDescription: z.string().optional(),
95
- schemaName: z.string().optional(),
96
- schemaIdentifier: z.string().optional(),
97
65
  agentSchema: z.record(z.unknown()).optional(),
98
- agentSchemaIdentifier: z.string().optional(),
99
- skipSchema: z.boolean().default(false),
100
66
  requestTimeout: z.number().positive().default(30000),
101
- connectTimeout: z.number().positive().default(1e4),
102
67
  maxRetries: z.number().int().nonnegative().default(3),
103
68
  initialRetryDelay: z.number().positive().default(1000),
104
69
  maxRetryDelay: z.number().positive().default(60000),
105
- retryMultiplier: z.number().positive().default(2)
70
+ retryMultiplier: z.number().positive().default(2),
71
+ retryOnStatusCodes: z.array(HttpStatusCodeSchema).default([...DEFAULT_RETRY_ON_STATUS_CODES])
106
72
  });
107
73
  var PartialHttpConfigSchema = z.object({
108
74
  apiUrl: z.string().url(),
@@ -111,20 +77,16 @@ var PartialHttpConfigSchema = z.object({
111
77
  agentIdentifier: z.string().optional(),
112
78
  agentName: z.string().optional(),
113
79
  agentDescription: z.string().optional(),
114
- schemaName: z.string().optional(),
115
- schemaIdentifier: z.string().optional(),
116
80
  agentSchema: z.record(z.unknown()).optional(),
117
- agentSchemaIdentifier: z.string().optional(),
118
- skipSchema: z.boolean().optional(),
119
81
  requestTimeout: z.number().positive().optional(),
120
- connectTimeout: z.number().positive().optional(),
121
82
  maxRetries: z.number().int().nonnegative().optional(),
122
83
  initialRetryDelay: z.number().positive().optional(),
123
84
  maxRetryDelay: z.number().positive().optional(),
124
- retryMultiplier: z.number().positive().optional()
85
+ retryMultiplier: z.number().positive().optional(),
86
+ retryOnStatusCodes: z.array(HttpStatusCodeSchema).optional()
125
87
  });
126
88
  var ConfigSchema = z.object({
127
- transportType: z.enum(["stdio", "http"]).default("stdio"),
89
+ transportType: z.enum(["http"]).default("http"),
128
90
  sampleRate: z.number().min(0).max(1).default(1),
129
91
  captureInputs: z.boolean().default(true),
130
92
  captureOutputs: z.boolean().default(true),
@@ -133,37 +95,69 @@ var ConfigSchema = z.object({
133
95
  httpConfig: PartialHttpConfigSchema.optional()
134
96
  });
135
97
  function createConfig(options) {
98
+ const retryOnStatusCodesFromEnv = parseRetryOnStatusCodesEnv(process.env.PREFACTOR_RETRY_ON_STATUS_CODES);
136
99
  const config = {
137
- transportType: options?.transportType ?? process.env.PREFACTOR_TRANSPORT ?? "stdio",
100
+ transportType: options?.transportType ?? process.env.PREFACTOR_TRANSPORT ?? "http",
138
101
  sampleRate: options?.sampleRate ?? parseFloat(process.env.PREFACTOR_SAMPLE_RATE ?? "1.0"),
139
102
  captureInputs: options?.captureInputs ?? process.env.PREFACTOR_CAPTURE_INPUTS !== "false",
140
103
  captureOutputs: options?.captureOutputs ?? process.env.PREFACTOR_CAPTURE_OUTPUTS !== "false",
141
104
  maxInputLength: options?.maxInputLength ?? parseInt(process.env.PREFACTOR_MAX_INPUT_LENGTH ?? "10000", 10),
142
105
  maxOutputLength: options?.maxOutputLength ?? parseInt(process.env.PREFACTOR_MAX_OUTPUT_LENGTH ?? "10000", 10),
143
- httpConfig: options?.httpConfig
106
+ httpConfig: options?.httpConfig ? {
107
+ ...options.httpConfig,
108
+ retryOnStatusCodes: options.httpConfig.retryOnStatusCodes ?? retryOnStatusCodesFromEnv
109
+ } : undefined
144
110
  };
145
111
  return ConfigSchema.parse(config);
146
112
  }
113
+ function parseRetryOnStatusCodesEnv(value) {
114
+ if (!value) {
115
+ return;
116
+ }
117
+ const parsedCodes = value.split(",").map((status) => status.trim()).filter((status) => /^\d{3}$/.test(status)).map((status) => Number(status)).filter((status) => status >= 100 && status <= 599);
118
+ return parsedCodes.length > 0 ? parsedCodes : undefined;
119
+ }
147
120
  // packages/core/src/create-core.ts
148
121
  import { extractPartition } from "@prefactor/pfid";
149
122
 
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);
123
+ // packages/core/src/tracing/active-tracer.ts
124
+ var activeTracer;
125
+ function setActiveTracer(tracer) {
126
+ activeTracer = tracer;
127
+ }
128
+ function getActiveTracer() {
129
+ return activeTracer;
130
+ }
131
+ function clearActiveTracer(tracer) {
132
+ if (!tracer || activeTracer === tracer) {
133
+ activeTracer = undefined;
160
134
  }
161
- size() {
162
- return this.items.length;
135
+ }
136
+
137
+ // packages/core/src/lifecycle.ts
138
+ var shutdownHandlers = new Map;
139
+ var activeCoreRuntime = null;
140
+ function setActiveCoreRuntime(runtime) {
141
+ activeCoreRuntime = runtime;
142
+ }
143
+ function registerShutdownHandler(key, handler) {
144
+ shutdownHandlers.set(key, handler);
145
+ return () => shutdownHandlers.delete(key);
146
+ }
147
+ async function shutdown() {
148
+ for (const handler of shutdownHandlers.values()) {
149
+ try {
150
+ await handler();
151
+ } catch (error) {
152
+ console.error("Error during shutdown handler execution:", error);
153
+ }
163
154
  }
164
- async flush() {
165
- return;
155
+ if (activeCoreRuntime) {
156
+ const runtime = activeCoreRuntime;
157
+ activeCoreRuntime = null;
158
+ await runtime.shutdown();
166
159
  }
160
+ clearActiveTracer();
167
161
  }
168
162
 
169
163
  // packages/core/src/tracing/tracer.ts
@@ -214,38 +208,11 @@ class SpanContext {
214
208
  }
215
209
 
216
210
  // packages/core/src/tracing/span.ts
217
- var SpanType;
218
- ((SpanType2) => {
219
- SpanType2["AGENT"] = "agent";
220
- SpanType2["LLM"] = "llm";
221
- SpanType2["TOOL"] = "tool";
222
- SpanType2["CHAIN"] = "chain";
223
- SpanType2["RETRIEVER"] = "retriever";
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
- }
211
+ var SpanType = {
212
+ AGENT: "agent",
213
+ LLM: "llm",
214
+ TOOL: "tool",
215
+ CHAIN: "chain"
249
216
  };
250
217
  var SpanStatus;
251
218
  ((SpanStatus2) => {
@@ -256,10 +223,10 @@ var SpanStatus;
256
223
 
257
224
  // packages/core/src/tracing/tracer.ts
258
225
  class Tracer {
259
- queue;
226
+ transport;
260
227
  partition;
261
- constructor(queue, partition) {
262
- this.queue = queue;
228
+ constructor(transport, partition) {
229
+ this.transport = transport;
263
230
  this.partition = partition ?? generatePartition();
264
231
  }
265
232
  startSpan(options) {
@@ -279,14 +246,13 @@ class Tracer {
279
246
  outputs: null,
280
247
  tokenUsage: null,
281
248
  error: null,
282
- metadata: options.metadata ?? {},
283
- tags: options.tags ?? []
249
+ metadata: options.metadata ?? {}
284
250
  };
285
- if (options.spanType === "agent" /* AGENT */) {
251
+ if (options.spanType === SpanType.AGENT) {
286
252
  try {
287
- this.queue.enqueue({ type: "span_end", data: span });
253
+ this.transport.emit(span);
288
254
  } catch (error) {
289
- console.error("Failed to enqueue agent span:", error);
255
+ console.error("Failed to emit agent span:", error);
290
256
  }
291
257
  }
292
258
  return span;
@@ -307,28 +273,180 @@ class Tracer {
307
273
  span.status = "success" /* SUCCESS */;
308
274
  }
309
275
  try {
310
- if (span.spanType === "agent" /* AGENT */) {
311
- this.queue.enqueue({ type: "span_finish", data: { spanId: span.spanId, endTime } });
276
+ if (span.spanType === SpanType.AGENT) {
277
+ this.transport.finishSpan(span.spanId, endTime);
312
278
  } else {
313
- this.queue.enqueue({ type: "span_end", data: span });
279
+ this.transport.emit(span);
314
280
  }
315
281
  } catch (error) {
316
- console.error("Failed to enqueue span action:", error);
282
+ console.error("Failed to emit span action:", error);
317
283
  }
318
284
  }
319
285
  async close() {
320
286
  try {
321
- await this.queue.flush();
287
+ await this.transport.close();
288
+ } catch (error) {
289
+ console.error("Failed to close transport:", error);
290
+ }
291
+ }
292
+ startAgentInstance() {
293
+ try {
294
+ this.transport.startAgentInstance();
295
+ } catch (error) {
296
+ console.error("Failed to start agent instance:", error);
297
+ }
298
+ }
299
+ finishAgentInstance() {
300
+ try {
301
+ this.transport.finishAgentInstance();
322
302
  } catch (error) {
323
- console.error("Failed to flush queue:", error);
303
+ console.error("Failed to finish agent instance:", error);
304
+ }
305
+ }
306
+ }
307
+
308
+ // packages/core/src/queue/in-memory-queue.ts
309
+ class InMemoryQueue {
310
+ items = [];
311
+ waiters = [];
312
+ isClosed = false;
313
+ async put(item) {
314
+ if (this.isClosed) {
315
+ throw new Error("Cannot put item into a closed queue");
316
+ }
317
+ const waiter = this.waiters.shift();
318
+ if (waiter) {
319
+ waiter({ done: false, item });
320
+ return;
321
+ }
322
+ this.items.push({ item });
323
+ }
324
+ async get() {
325
+ if (this.items.length > 0) {
326
+ const entry = this.items.shift();
327
+ if (entry) {
328
+ return { done: false, item: entry.item };
329
+ }
330
+ }
331
+ if (this.isClosed) {
332
+ return { done: true };
333
+ }
334
+ return new Promise((resolve) => {
335
+ this.waiters.push(resolve);
336
+ });
337
+ }
338
+ close() {
339
+ if (this.isClosed) {
340
+ return;
341
+ }
342
+ this.isClosed = true;
343
+ while (this.waiters.length > 0) {
344
+ const waiter = this.waiters.shift();
345
+ if (waiter) {
346
+ waiter({ done: true });
347
+ }
348
+ }
349
+ }
350
+ size() {
351
+ return this.items.length;
352
+ }
353
+ }
354
+
355
+ // packages/core/src/queue/task-executor.ts
356
+ var DEFAULT_WORKER_COUNT = 1;
357
+ var DEFAULT_MAX_RETRIES = 0;
358
+ var DEFAULT_RETRY_DELAY_MS = 0;
359
+
360
+ class TaskExecutor {
361
+ queue;
362
+ handler;
363
+ isRunning = false;
364
+ workerPromises = [];
365
+ workerCount;
366
+ maxRetries;
367
+ retryDelayMs;
368
+ onError;
369
+ constructor(queue, handler, options = {}) {
370
+ this.queue = queue;
371
+ this.handler = handler;
372
+ this.workerCount = Math.max(options.workerCount ?? DEFAULT_WORKER_COUNT, 1);
373
+ this.maxRetries = Math.max(options.maxRetries ?? DEFAULT_MAX_RETRIES, 0);
374
+ this.retryDelayMs = Math.max(options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS, 0);
375
+ this.onError = options.onError;
376
+ }
377
+ start() {
378
+ if (this.isRunning) {
379
+ return;
380
+ }
381
+ this.isRunning = true;
382
+ this.workerPromises = Array.from({ length: this.workerCount }, () => this.runWorker());
383
+ }
384
+ async stop() {
385
+ if (!this.isRunning) {
386
+ return;
387
+ }
388
+ this.isRunning = false;
389
+ this.queue.close();
390
+ await Promise.all(this.workerPromises);
391
+ this.workerPromises = [];
392
+ }
393
+ async runWorker() {
394
+ while (true) {
395
+ const result = await this.queue.get();
396
+ if (result.done) {
397
+ return;
398
+ }
399
+ await this.executeWithRetry(result.item);
400
+ }
401
+ }
402
+ async executeWithRetry(item) {
403
+ let attempt = 0;
404
+ while (attempt <= this.maxRetries) {
405
+ try {
406
+ await this.handler(item);
407
+ return;
408
+ } catch (error) {
409
+ if (attempt >= this.maxRetries) {
410
+ await this.safeOnError(error, item);
411
+ return;
412
+ }
413
+ if (this.retryDelayMs > 0) {
414
+ await new Promise((resolve) => setTimeout(resolve, this.retryDelayMs));
415
+ }
416
+ }
417
+ attempt += 1;
418
+ }
419
+ }
420
+ async safeOnError(error, item) {
421
+ if (!this.onError) {
422
+ return;
423
+ }
424
+ try {
425
+ await this.onError(error, item);
426
+ } catch {
427
+ return;
324
428
  }
325
429
  }
326
430
  }
327
431
 
328
432
  // packages/core/src/utils/logging.ts
433
+ var LogLevel;
434
+ ((LogLevel2) => {
435
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
436
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
437
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
438
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
439
+ })(LogLevel ||= {});
440
+
329
441
  class Logger {
330
442
  namespace;
331
- static level = 1 /* INFO */;
443
+ static level = (() => {
444
+ const level = process.env.PREFACTOR_LOG_LEVEL?.toUpperCase();
445
+ if (level && level in LogLevel) {
446
+ return LogLevel[level];
447
+ }
448
+ return 1 /* INFO */;
449
+ })();
332
450
  constructor(namespace) {
333
451
  this.namespace = namespace;
334
452
  }
@@ -372,72 +490,331 @@ function configureLogging() {
372
490
  }
373
491
  }
374
492
 
493
+ // packages/core/src/transport/http/agent-instance-client.ts
494
+ class AgentInstanceClient {
495
+ httpClient;
496
+ constructor(httpClient) {
497
+ this.httpClient = httpClient;
498
+ }
499
+ register(payload) {
500
+ return this.httpClient.request("/api/v1/agent_instance/register", {
501
+ method: "POST",
502
+ body: payload
503
+ });
504
+ }
505
+ async start(agentInstanceId) {
506
+ await this.httpClient.request(`/api/v1/agent_instance/${agentInstanceId}/start`, {
507
+ method: "POST",
508
+ body: {}
509
+ });
510
+ }
511
+ async finish(agentInstanceId) {
512
+ await this.httpClient.request(`/api/v1/agent_instance/${agentInstanceId}/finish`, {
513
+ method: "POST",
514
+ body: {}
515
+ });
516
+ }
517
+ }
518
+
519
+ // packages/core/src/transport/http/retry-policy.ts
520
+ var JITTER_MIN = 0.5;
521
+ function shouldRetryStatusCode(statusCode, retryOnStatusCodes) {
522
+ return retryOnStatusCodes.includes(statusCode);
523
+ }
524
+ function calculateRetryDelay(attempt, config, random = Math.random) {
525
+ const baseDelay = Math.min(config.initialRetryDelay * config.retryMultiplier ** attempt, config.maxRetryDelay);
526
+ const jitterMultiplier = JITTER_MIN + random() * JITTER_MIN;
527
+ return Math.round(baseDelay * jitterMultiplier);
528
+ }
529
+
530
+ // packages/core/src/transport/http/http-client.ts
531
+ class HttpClientError extends Error {
532
+ url;
533
+ method;
534
+ status;
535
+ statusText;
536
+ responseBody;
537
+ retryable;
538
+ constructor(message, options) {
539
+ super(message, { cause: options.cause });
540
+ this.name = "HttpClientError";
541
+ this.url = options.url;
542
+ this.method = options.method;
543
+ this.status = options.status;
544
+ this.statusText = options.statusText;
545
+ this.responseBody = options.responseBody;
546
+ this.retryable = options.retryable;
547
+ }
548
+ }
549
+
550
+ class HttpClient {
551
+ config;
552
+ fetchFn;
553
+ sleep;
554
+ random;
555
+ constructor(config, dependencies = {}) {
556
+ this.config = config;
557
+ this.fetchFn = dependencies.fetchFn ?? fetch;
558
+ this.sleep = dependencies.sleep ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
559
+ this.random = dependencies.random ?? Math.random;
560
+ }
561
+ async request(path, options = {}) {
562
+ const url = new URL(path, this.config.apiUrl).toString();
563
+ const method = options.method ?? "GET";
564
+ let attempt = 0;
565
+ while (true) {
566
+ const headers = new Headers(options.headers);
567
+ headers.set("Authorization", `Bearer ${this.config.apiToken}`);
568
+ if (options.body !== undefined && !headers.has("Content-Type")) {
569
+ headers.set("Content-Type", "application/json");
570
+ }
571
+ const requestInit = {
572
+ ...options,
573
+ method,
574
+ headers,
575
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
576
+ signal: AbortSignal.timeout(options.timeoutMs ?? this.config.requestTimeout)
577
+ };
578
+ try {
579
+ const response = await this.fetchFn(url, requestInit);
580
+ if (response.ok) {
581
+ return await parseResponseBody(response);
582
+ }
583
+ const responseBody = await parseResponseBody(response);
584
+ const canRetry = attempt < this.config.maxRetries && shouldRetryStatusCode(response.status, this.config.retryOnStatusCodes);
585
+ if (canRetry) {
586
+ const delayMs = calculateRetryDelay(attempt, this.config, this.random);
587
+ await this.sleep(delayMs);
588
+ attempt += 1;
589
+ continue;
590
+ }
591
+ throw new HttpClientError(`HTTP request failed with status ${response.status}`, {
592
+ url,
593
+ method,
594
+ status: response.status,
595
+ statusText: response.statusText,
596
+ responseBody,
597
+ retryable: shouldRetryStatusCode(response.status, this.config.retryOnStatusCodes)
598
+ });
599
+ } catch (error) {
600
+ if (error instanceof HttpClientError) {
601
+ throw error;
602
+ }
603
+ const canRetry = attempt < this.config.maxRetries && isRetryableNetworkError(error);
604
+ if (canRetry) {
605
+ const delayMs = calculateRetryDelay(attempt, this.config, this.random);
606
+ await this.sleep(delayMs);
607
+ attempt += 1;
608
+ continue;
609
+ }
610
+ throw new HttpClientError("HTTP request failed due to network error", {
611
+ url,
612
+ method,
613
+ retryable: false,
614
+ cause: error
615
+ });
616
+ }
617
+ }
618
+ }
619
+ }
620
+ function isRetryableNetworkError(error) {
621
+ if (error instanceof TypeError) {
622
+ return true;
623
+ }
624
+ if (error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError")) {
625
+ return true;
626
+ }
627
+ if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) {
628
+ return true;
629
+ }
630
+ return false;
631
+ }
632
+ async function parseResponseBody(response) {
633
+ const bodyText = await response.text();
634
+ if (!bodyText) {
635
+ return null;
636
+ }
637
+ const contentType = response.headers.get("content-type") ?? "";
638
+ if (contentType.includes("application/json")) {
639
+ try {
640
+ return JSON.parse(bodyText);
641
+ } catch {
642
+ return bodyText;
643
+ }
644
+ }
645
+ try {
646
+ return JSON.parse(bodyText);
647
+ } catch {
648
+ return bodyText;
649
+ }
650
+ }
651
+
652
+ // packages/core/src/transport/http/agent-span-client.ts
653
+ class AgentSpanClient {
654
+ httpClient;
655
+ constructor(httpClient) {
656
+ this.httpClient = httpClient;
657
+ }
658
+ create(payload) {
659
+ return this.httpClient.request("/api/v1/agent_spans", {
660
+ method: "POST",
661
+ body: payload
662
+ });
663
+ }
664
+ async finish(spanId, timestamp) {
665
+ try {
666
+ await this.httpClient.request(`/api/v1/agent_spans/${spanId}/finish`, {
667
+ method: "POST",
668
+ body: { timestamp }
669
+ });
670
+ } catch (error) {
671
+ if (error instanceof HttpClientError && error.status === 409 && isAlreadyFinishedError(error.responseBody)) {
672
+ return;
673
+ }
674
+ throw error;
675
+ }
676
+ }
677
+ }
678
+ function isAlreadyFinishedError(responseBody) {
679
+ if (!responseBody || typeof responseBody !== "object") {
680
+ return false;
681
+ }
682
+ const payload = responseBody;
683
+ return payload.code === "invalid_action";
684
+ }
685
+
375
686
  // packages/core/src/transport/http.ts
376
687
  var logger = getLogger("http-transport");
377
688
 
378
689
  class HttpTransport {
379
690
  config;
380
691
  closed = false;
692
+ actionQueue = new InMemoryQueue;
693
+ taskExecutor;
694
+ agentInstanceClient;
695
+ agentSpanClient;
696
+ previousAgentSchema = null;
697
+ requiresNewAgentIdentifier = false;
698
+ previousAgentIdentifier = null;
381
699
  agentInstanceId = null;
382
700
  spanIdMap = new Map;
383
701
  pendingFinishes = new Map;
702
+ pendingChildren = new Map;
384
703
  constructor(config) {
385
704
  this.config = config;
705
+ const httpClient = new HttpClient(config);
706
+ this.agentInstanceClient = new AgentInstanceClient(httpClient);
707
+ this.agentSpanClient = new AgentSpanClient(httpClient);
708
+ this.taskExecutor = new TaskExecutor(this.actionQueue, this.processAction, {
709
+ workerCount: 1,
710
+ onError: async (error) => {
711
+ logger.error("Error processing HTTP action:", error);
712
+ }
713
+ });
714
+ this.taskExecutor.start();
715
+ }
716
+ registerSchema(schema) {
717
+ this.enqueue({ type: "schema_register", schema });
718
+ }
719
+ startAgentInstance(options) {
720
+ this.enqueue({ type: "agent_start", options });
721
+ }
722
+ finishAgentInstance() {
723
+ this.enqueue({ type: "agent_finish" });
724
+ }
725
+ emit(span) {
726
+ this.enqueue({ type: "span_end", span });
727
+ }
728
+ finishSpan(spanId, endTime) {
729
+ this.enqueue({ type: "span_finish", spanId, endTime });
730
+ }
731
+ async close() {
732
+ this.closed = true;
733
+ await this.taskExecutor.stop();
734
+ if (this.pendingFinishes.size > 0) {
735
+ logger.warn(`Transport closed with ${this.pendingFinishes.size} pending span finish(es) that could not be processed`);
736
+ this.pendingFinishes.clear();
737
+ }
738
+ if (this.pendingChildren.size > 0) {
739
+ logger.warn(`Transport closed with ${this.pendingChildren.size} unresolved parent span reference(s)`);
740
+ this.pendingChildren.clear();
741
+ }
386
742
  }
387
- async processBatch(items) {
388
- if (this.closed || items.length === 0) {
743
+ enqueue(action) {
744
+ if (this.closed) {
389
745
  return;
390
746
  }
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;
747
+ this.actionQueue.put(action).catch((error) => {
748
+ logger.error("Failed to enqueue HTTP action:", error);
749
+ });
750
+ }
751
+ processAction = async (action) => {
752
+ switch (action.type) {
753
+ case "schema_register": {
754
+ const incomingSchema = JSON.stringify(action.schema);
755
+ if (this.previousAgentSchema !== null && this.previousAgentSchema !== incomingSchema) {
756
+ this.requiresNewAgentIdentifier = true;
757
+ this.previousAgentIdentifier = this.config.agentIdentifier;
758
+ this.agentInstanceId = null;
759
+ }
760
+ this.previousAgentSchema = incomingSchema;
761
+ this.config.agentSchema = action.schema;
762
+ return;
397
763
  }
398
- }
399
- for (const item of items) {
400
- try {
401
- switch (item.type) {
402
- case "schema_register":
403
- break;
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;
410
- await this.startAgentInstanceHttp();
411
- break;
412
- case "agent_finish":
413
- await this.finishAgentInstanceHttp();
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;
764
+ case "agent_start": {
765
+ if (this.requiresNewAgentIdentifier) {
766
+ const nextAgentIdentifier = action.options?.agentIdentifier;
767
+ if (nextAgentIdentifier === undefined || nextAgentIdentifier === this.previousAgentIdentifier) {
768
+ logger.error("Schema changed; starting an agent requires a new agentIdentifier value.");
769
+ return;
431
770
  }
771
+ this.requiresNewAgentIdentifier = false;
772
+ this.previousAgentIdentifier = null;
432
773
  }
433
- } catch (error) {
434
- logger.error("Error processing batch item:", error);
774
+ if (action.options?.agentId !== undefined)
775
+ this.config.agentId = action.options.agentId;
776
+ if (action.options?.agentIdentifier !== undefined) {
777
+ this.config.agentIdentifier = action.options.agentIdentifier;
778
+ }
779
+ if (action.options?.agentName !== undefined)
780
+ this.config.agentName = action.options.agentName;
781
+ if (action.options?.agentDescription !== undefined) {
782
+ this.config.agentDescription = action.options.agentDescription;
783
+ }
784
+ await this.startAgentInstanceHttp();
785
+ return;
786
+ }
787
+ case "agent_finish":
788
+ await this.finishAgentInstanceHttp();
789
+ return;
790
+ case "span_end":
791
+ if (!this.agentInstanceId) {
792
+ await this.ensureAgentRegistered();
793
+ }
794
+ if (action.span.parentSpanId && !this.spanIdMap.has(action.span.parentSpanId)) {
795
+ this.queuePendingChild(action.span.parentSpanId, action.span);
796
+ return;
797
+ }
798
+ await this.sendSpan(action.span);
799
+ return;
800
+ case "span_finish": {
801
+ const backendSpanId = this.spanIdMap.get(action.spanId);
802
+ if (backendSpanId) {
803
+ const timestamp = new Date(action.endTime).toISOString();
804
+ await this.finishSpanHttp({ spanId: action.spanId, timestamp });
805
+ } else {
806
+ this.pendingFinishes.set(action.spanId, action.endTime);
807
+ }
808
+ return;
435
809
  }
436
810
  }
437
- }
811
+ };
438
812
  async processPendingFinishes(spanId) {
813
+ if (!this.pendingFinishes.has(spanId)) {
814
+ return;
815
+ }
439
816
  const pendingEndTime = this.pendingFinishes.get(spanId);
440
- if (!pendingEndTime) {
817
+ if (pendingEndTime === undefined) {
441
818
  return;
442
819
  }
443
820
  try {
@@ -448,56 +825,48 @@ class HttpTransport {
448
825
  logger.error("Error processing pending span finish:", error);
449
826
  }
450
827
  }
451
- async sendSpan(span, retry = 0) {
452
- const url = `${this.config.apiUrl}/api/v1/agent_spans`;
828
+ async sendSpan(span) {
453
829
  const payload = this.transformSpanToApiFormat(span);
454
830
  try {
455
- const response = await fetch(url, {
456
- method: "POST",
457
- headers: {
458
- Authorization: `Bearer ${this.config.apiToken}`,
459
- "Content-Type": "application/json"
460
- },
461
- body: JSON.stringify(payload),
462
- signal: AbortSignal.timeout(this.config.requestTimeout)
463
- });
464
- if (response.ok) {
465
- const data = await response.json();
466
- const backendSpanId = data?.details?.id;
467
- if (backendSpanId) {
468
- this.spanIdMap.set(span.spanId, backendSpanId);
469
- await this.processPendingFinishes(span.spanId);
470
- }
831
+ const response = await this.agentSpanClient.create(payload);
832
+ const backendSpanId = response.details?.id;
833
+ if (!backendSpanId) {
471
834
  return;
472
835
  }
473
- if ((response.status >= 500 || response.status === 429) && retry < this.config.maxRetries) {
474
- const delay = Math.min(this.config.initialRetryDelay * this.config.retryMultiplier ** retry, this.config.maxRetryDelay);
475
- logger.debug(`Retrying span send after ${delay}ms (attempt ${retry + 1})`);
476
- await new Promise((resolve) => setTimeout(resolve, delay));
477
- return this.sendSpan(span, retry + 1);
478
- }
479
- logger.error(`Failed to send span: ${response.status} ${response.statusText}`);
836
+ this.spanIdMap.set(span.spanId, backendSpanId);
837
+ await this.processPendingFinishes(span.spanId);
838
+ await this.processPendingChildren(span.spanId);
480
839
  } catch (error) {
481
840
  logger.error("Error sending span:", error);
482
- if (retry < this.config.maxRetries) {
483
- const delay = Math.min(this.config.initialRetryDelay * this.config.retryMultiplier ** retry, this.config.maxRetryDelay);
484
- await new Promise((resolve) => setTimeout(resolve, delay));
485
- return this.sendSpan(span, retry + 1);
486
- }
841
+ }
842
+ }
843
+ queuePendingChild(parentSpanId, childSpan) {
844
+ const existingChildren = this.pendingChildren.get(parentSpanId) ?? [];
845
+ existingChildren.push(childSpan);
846
+ this.pendingChildren.set(parentSpanId, existingChildren);
847
+ }
848
+ async processPendingChildren(parentSpanId) {
849
+ const waitingChildren = this.pendingChildren.get(parentSpanId);
850
+ if (!waitingChildren || waitingChildren.length === 0) {
851
+ return;
852
+ }
853
+ this.pendingChildren.delete(parentSpanId);
854
+ for (const childSpan of waitingChildren) {
855
+ await this.sendSpan(childSpan);
487
856
  }
488
857
  }
489
858
  transformSpanToApiFormat(span) {
490
859
  const startedAt = new Date(span.startTime).toISOString();
491
860
  const finishedAt = span.endTime ? new Date(span.endTime).toISOString() : null;
861
+ const apiStatus = this.mapStatusForApi(span.status);
492
862
  const payload = {
493
863
  span_id: span.spanId,
494
864
  trace_id: span.traceId,
495
865
  name: span.name,
496
- status: span.status,
866
+ status: apiStatus,
497
867
  inputs: span.inputs,
498
868
  outputs: span.outputs,
499
869
  metadata: span.metadata,
500
- tags: span.tags,
501
870
  token_usage: null,
502
871
  error: null
503
872
  };
@@ -520,6 +889,7 @@ class HttpTransport {
520
889
  details: {
521
890
  agent_instance_id: this.agentInstanceId,
522
891
  schema_name: span.spanType,
892
+ status: apiStatus,
523
893
  payload,
524
894
  parent_span_id: parentSpanId,
525
895
  started_at: startedAt,
@@ -527,14 +897,22 @@ class HttpTransport {
527
897
  }
528
898
  };
529
899
  }
530
- getDefaultSchema() {
531
- return DEFAULT_AGENT_SCHEMA;
900
+ mapStatusForApi(status) {
901
+ switch (status) {
902
+ case "running":
903
+ return "active";
904
+ case "success":
905
+ return "complete";
906
+ case "error":
907
+ return "failed";
908
+ default:
909
+ return "active";
910
+ }
532
911
  }
533
912
  async ensureAgentRegistered() {
534
913
  if (this.agentInstanceId) {
535
- return;
914
+ return true;
536
915
  }
537
- const url = `${this.config.apiUrl}/api/v1/agent_instance/register`;
538
916
  const payload = {};
539
917
  if (this.config.agentId)
540
918
  payload.agent_id = this.config.agentId;
@@ -545,61 +923,25 @@ class HttpTransport {
545
923
  description: this.config.agentDescription || ""
546
924
  };
547
925
  }
548
- if (this.config.skipSchema) {
549
- logger.debug("Skipping schema in registration (skipSchema=true)");
550
- } else if (this.config.agentSchema) {
551
- logger.debug("Using custom agent schema");
926
+ if (this.config.agentSchema) {
552
927
  payload.agent_schema_version = this.config.agentSchema;
553
- } else if (this.config.agentSchemaIdentifier) {
554
- logger.debug(`Using schema version: ${this.config.agentSchemaIdentifier}`);
555
- payload.agent_schema_version = {
556
- external_identifier: this.config.agentSchemaIdentifier
557
- };
558
- } else {
559
- logger.debug("Using default hardcoded schema (v1.0.0)");
560
- payload.agent_schema_version = this.getDefaultSchema();
561
928
  }
562
929
  try {
563
- const response = await fetch(url, {
564
- method: "POST",
565
- headers: {
566
- Authorization: `Bearer ${this.config.apiToken}`,
567
- "Content-Type": "application/json"
568
- },
569
- body: JSON.stringify(payload),
570
- signal: AbortSignal.timeout(this.config.requestTimeout)
571
- });
572
- if (response.ok) {
573
- const data = await response.json();
574
- this.agentInstanceId = data?.details?.id ?? null;
575
- logger.debug(`Registered agent instance: ${this.agentInstanceId}`);
576
- } else {
577
- logger.error(`Failed to register agent: ${response.status} ${response.statusText}`);
578
- }
930
+ const data = await this.agentInstanceClient.register(payload);
931
+ this.agentInstanceId = data.details?.id ?? null;
579
932
  } catch (error) {
580
933
  logger.error("Error registering agent:", error);
581
934
  }
935
+ return this.agentInstanceId !== null;
582
936
  }
583
937
  async startAgentInstanceHttp() {
584
- await this.ensureAgentRegistered();
585
- if (!this.agentInstanceId) {
938
+ const isRegistered = await this.ensureAgentRegistered();
939
+ if (!isRegistered || !this.agentInstanceId) {
586
940
  logger.error("Cannot start agent instance: not registered");
587
941
  return;
588
942
  }
589
- const url = `${this.config.apiUrl}/api/v1/agent_instance/${this.agentInstanceId}/start`;
590
943
  try {
591
- const response = await fetch(url, {
592
- method: "POST",
593
- headers: {
594
- Authorization: `Bearer ${this.config.apiToken}`,
595
- "Content-Type": "application/json"
596
- },
597
- body: JSON.stringify({}),
598
- signal: AbortSignal.timeout(this.config.requestTimeout)
599
- });
600
- if (!response.ok) {
601
- logger.error(`Failed to start agent instance: ${response.status} ${response.statusText}`);
602
- }
944
+ await this.agentInstanceClient.start(this.agentInstanceId);
603
945
  } catch (error) {
604
946
  logger.error("Error starting agent instance:", error);
605
947
  }
@@ -609,20 +951,8 @@ class HttpTransport {
609
951
  logger.error("Cannot finish agent instance: not registered");
610
952
  return;
611
953
  }
612
- const url = `${this.config.apiUrl}/api/v1/agent_instance/${this.agentInstanceId}/finish`;
613
954
  try {
614
- const response = await fetch(url, {
615
- method: "POST",
616
- headers: {
617
- Authorization: `Bearer ${this.config.apiToken}`,
618
- "Content-Type": "application/json"
619
- },
620
- body: JSON.stringify({}),
621
- signal: AbortSignal.timeout(this.config.requestTimeout)
622
- });
623
- if (!response.ok) {
624
- logger.error(`Failed to finish agent instance: ${response.status} ${response.statusText}`);
625
- }
955
+ await this.agentInstanceClient.finish(this.agentInstanceId);
626
956
  } catch (error) {
627
957
  logger.error("Error finishing agent instance:", error);
628
958
  }
@@ -634,33 +964,76 @@ class HttpTransport {
634
964
  logger.warn(`Cannot finish span ${data.spanId}: backend ID not found`);
635
965
  return;
636
966
  }
637
- const url = `${this.config.apiUrl}/api/v1/agent_spans/${backendSpanId}/finish`;
638
967
  try {
639
- const response = await fetch(url, {
640
- method: "POST",
641
- headers: {
642
- Authorization: `Bearer ${this.config.apiToken}`,
643
- "Content-Type": "application/json"
644
- },
645
- body: JSON.stringify({ timestamp: data.timestamp }),
646
- signal: AbortSignal.timeout(this.config.requestTimeout)
647
- });
648
- if (!response.ok) {
649
- logger.error(`Failed to finish span: ${response.status} ${response.statusText}`);
650
- }
968
+ await this.agentSpanClient.finish(backendSpanId, data.timestamp);
651
969
  } catch (error) {
652
970
  logger.error("Error finishing span:", error);
653
971
  }
654
972
  }
655
- async close() {
656
- this.closed = true;
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();
973
+ }
974
+
975
+ // packages/core/src/create-core.ts
976
+ function createCore(config) {
977
+ if (!config.httpConfig) {
978
+ throw new Error("HTTP transport requires httpConfig to be provided in configuration");
979
+ }
980
+ const httpConfig = HttpTransportConfigSchema.parse(config.httpConfig);
981
+ const transport = new HttpTransport(httpConfig);
982
+ let partition;
983
+ if (config.httpConfig.agentId) {
984
+ try {
985
+ partition = extractPartition(config.httpConfig.agentId);
986
+ } catch {
987
+ partition = undefined;
660
988
  }
661
989
  }
990
+ const tracer = new Tracer(transport, partition);
991
+ setActiveTracer(tracer);
992
+ const allowUnregisteredSchema = Boolean(config.httpConfig.agentSchema);
993
+ const agentManager = new AgentInstanceManager(transport, {
994
+ allowUnregisteredSchema
995
+ });
996
+ const shutdown2 = async () => {
997
+ await tracer.close();
998
+ clearActiveTracer(tracer);
999
+ setActiveCoreRuntime(null);
1000
+ };
1001
+ const runtime = { tracer, agentManager, shutdown: shutdown2 };
1002
+ setActiveCoreRuntime(runtime);
1003
+ return runtime;
1004
+ }
1005
+ // packages/core/src/tracing/with-span.ts
1006
+ async function withSpan(tracerOrOptions, optionsOrFn, maybeFn) {
1007
+ const { tracer, options, fn } = resolveArgs(tracerOrOptions, optionsOrFn, maybeFn);
1008
+ const span = tracer.startSpan(options);
1009
+ try {
1010
+ const result = await SpanContext.runAsync(span, async () => await fn());
1011
+ tracer.endSpan(span);
1012
+ return result;
1013
+ } catch (error) {
1014
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
1015
+ tracer.endSpan(span, { error: normalizedError });
1016
+ throw error;
1017
+ }
1018
+ }
1019
+ function resolveArgs(tracerOrOptions, optionsOrFn, maybeFn) {
1020
+ if (maybeFn) {
1021
+ return {
1022
+ tracer: tracerOrOptions,
1023
+ options: optionsOrFn,
1024
+ fn: maybeFn
1025
+ };
1026
+ }
1027
+ const tracer = getActiveTracer();
1028
+ if (!tracer) {
1029
+ throw new Error("No active tracer found. Initialize Prefactor first or pass a tracer explicitly.");
1030
+ }
1031
+ return {
1032
+ tracer,
1033
+ options: tracerOrOptions,
1034
+ fn: optionsOrFn
1035
+ };
662
1036
  }
663
-
664
1037
  // packages/core/src/utils/serialization.ts
665
1038
  function truncateString(value, maxLength) {
666
1039
  if (value.length <= maxLength) {
@@ -694,173 +1067,25 @@ function serializeValue(value, maxLength = 1e4) {
694
1067
  return `<${typeof value} object>`;
695
1068
  }
696
1069
  }
697
-
698
- // packages/core/src/transport/stdio.ts
699
- class StdioTransport {
700
- closed = false;
701
- writeLock = Promise.resolve();
702
- async processBatch(items) {
703
- if (this.closed || items.length === 0) {
704
- return;
705
- }
706
- this.writeLock = this.writeLock.then(async () => {
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}
712
- `);
713
- } catch (error) {
714
- console.error("Failed to emit queue action to stdout:", error);
715
- }
716
- }
717
- });
718
- await this.writeLock;
719
- }
720
- async close() {
721
- this.closed = true;
722
- await this.writeLock;
723
- }
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
- }
844
1070
  export {
1071
+ withSpan,
845
1072
  truncateString,
1073
+ shutdown,
846
1074
  serializeValue,
1075
+ registerShutdownHandler,
847
1076
  getLogger,
848
1077
  createCore,
849
1078
  createConfig,
850
1079
  configureLogging,
851
1080
  Tracer,
852
- StdioTransport,
853
1081
  SpanType,
854
1082
  SpanStatus,
855
1083
  SpanContext,
856
- SchemaRegistry,
857
1084
  PartialHttpConfigSchema,
858
- InMemoryQueue,
859
1085
  HttpTransportConfigSchema,
860
1086
  HttpTransport,
861
- DEFAULT_AGENT_SCHEMA,
862
1087
  ConfigSchema,
863
1088
  AgentInstanceManager
864
1089
  };
865
1090
 
866
- //# debugId=AE4689D591D1DB9C64756E2164756E21
1091
+ //# debugId=9E6C81B271F2076D64756E2164756E21