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