@prefactor/core 0.1.1 → 0.2.1

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