@prefactor/core 0.2.0 → 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/dist/agent/instance-manager.d.ts +5 -8
- package/dist/agent/instance-manager.d.ts.map +1 -1
- package/dist/agent/instance-manager.js +34 -55
- package/dist/agent/instance-manager.js.map +1 -1
- package/dist/config.d.ts +15 -63
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +29 -20
- package/dist/config.js.map +1 -1
- package/dist/create-core.d.ts +0 -2
- package/dist/create-core.d.ts.map +1 -1
- package/dist/create-core.js +10 -28
- package/dist/create-core.js.map +1 -1
- package/dist/index.cjs +577 -457
- package/dist/index.cjs.map +17 -15
- package/dist/index.d.ts +2 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +577 -457
- package/dist/index.js.map +17 -15
- package/dist/queue/actions.d.ts +15 -26
- package/dist/queue/actions.d.ts.map +1 -1
- package/dist/queue/base.d.ts +15 -3
- package/dist/queue/base.d.ts.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/span.d.ts +7 -14
- package/dist/tracing/span.d.ts.map +1 -1
- package/dist/tracing/span.js +5 -36
- package/dist/tracing/span.js.map +1 -1
- package/dist/tracing/tracer.d.ts +6 -7
- package/dist/tracing/tracer.d.ts.map +1 -1
- package/dist/tracing/tracer.js +27 -12
- 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 +31 -50
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +138 -227
- 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/agent/schema-registry.d.ts +0 -9
- package/dist/agent/schema-registry.d.ts.map +0 -1
- package/dist/agent/schema-registry.js +0 -16
- package/dist/agent/schema-registry.js.map +0 -1
- package/dist/queue/in-memory.d.ts +0 -9
- package/dist/queue/in-memory.d.ts.map +0 -1
- package/dist/queue/in-memory.js +0 -18
- package/dist/queue/in-memory.js.map +0 -1
- package/dist/transport/base.d.ts +0 -18
- package/dist/transport/base.d.ts.map +0 -1
- package/dist/transport/base.js +0 -2
- package/dist/transport/base.js.map +0 -1
- package/dist/transport/stdio.d.ts +0 -36
- package/dist/transport/stdio.d.ts.map +0 -1
- package/dist/transport/stdio.js +0 -56
- package/dist/transport/stdio.js.map +0 -1
- package/dist/transport/worker.d.ts +0 -22
- package/dist/transport/worker.d.ts.map +0 -1
- package/dist/transport/worker.js +0 -85
- 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
|
-
|
|
3
|
+
transport;
|
|
48
4
|
options;
|
|
49
|
-
|
|
50
|
-
constructor(
|
|
51
|
-
this.
|
|
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.
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
63
|
-
|
|
64
|
-
|
|
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 &&
|
|
72
|
-
console.warn(
|
|
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
|
-
|
|
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.
|
|
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(["
|
|
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,38 +95,30 @@ 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 ?? "
|
|
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
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// packages/core/src/queue/in-memory.ts
|
|
151
|
-
class InMemoryQueue {
|
|
152
|
-
items = [];
|
|
153
|
-
enqueue(item) {
|
|
154
|
-
this.items.push(item);
|
|
155
|
-
}
|
|
156
|
-
dequeueBatch(maxItems) {
|
|
157
|
-
if (this.items.length === 0)
|
|
158
|
-
return [];
|
|
159
|
-
return this.items.splice(0, maxItems);
|
|
160
|
-
}
|
|
161
|
-
size() {
|
|
162
|
-
return this.items.length;
|
|
163
|
-
}
|
|
164
|
-
async flush() {
|
|
113
|
+
function parseRetryOnStatusCodesEnv(value) {
|
|
114
|
+
if (!value) {
|
|
165
115
|
return;
|
|
166
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;
|
|
167
119
|
}
|
|
120
|
+
// packages/core/src/create-core.ts
|
|
121
|
+
import { extractPartition } from "@prefactor/pfid";
|
|
168
122
|
|
|
169
123
|
// packages/core/src/tracing/tracer.ts
|
|
170
124
|
import { generate, generatePartition } from "@prefactor/pfid";
|
|
@@ -214,38 +168,11 @@ class SpanContext {
|
|
|
214
168
|
}
|
|
215
169
|
|
|
216
170
|
// packages/core/src/tracing/span.ts
|
|
217
|
-
var SpanType
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
}
|
|
171
|
+
var SpanType = {
|
|
172
|
+
AGENT: "agent",
|
|
173
|
+
LLM: "llm",
|
|
174
|
+
TOOL: "tool",
|
|
175
|
+
CHAIN: "chain"
|
|
249
176
|
};
|
|
250
177
|
var SpanStatus;
|
|
251
178
|
((SpanStatus2) => {
|
|
@@ -256,10 +183,10 @@ var SpanStatus;
|
|
|
256
183
|
|
|
257
184
|
// packages/core/src/tracing/tracer.ts
|
|
258
185
|
class Tracer {
|
|
259
|
-
|
|
186
|
+
transport;
|
|
260
187
|
partition;
|
|
261
|
-
constructor(
|
|
262
|
-
this.
|
|
188
|
+
constructor(transport, partition) {
|
|
189
|
+
this.transport = transport;
|
|
263
190
|
this.partition = partition ?? generatePartition();
|
|
264
191
|
}
|
|
265
192
|
startSpan(options) {
|
|
@@ -279,14 +206,13 @@ class Tracer {
|
|
|
279
206
|
outputs: null,
|
|
280
207
|
tokenUsage: null,
|
|
281
208
|
error: null,
|
|
282
|
-
metadata: options.metadata ?? {}
|
|
283
|
-
tags: options.tags ?? []
|
|
209
|
+
metadata: options.metadata ?? {}
|
|
284
210
|
};
|
|
285
|
-
if (options.spanType ===
|
|
211
|
+
if (options.spanType === SpanType.AGENT) {
|
|
286
212
|
try {
|
|
287
|
-
this.
|
|
213
|
+
this.transport.emit(span);
|
|
288
214
|
} catch (error) {
|
|
289
|
-
console.error("Failed to
|
|
215
|
+
console.error("Failed to emit agent span:", error);
|
|
290
216
|
}
|
|
291
217
|
}
|
|
292
218
|
return span;
|
|
@@ -307,28 +233,180 @@ class Tracer {
|
|
|
307
233
|
span.status = "success" /* SUCCESS */;
|
|
308
234
|
}
|
|
309
235
|
try {
|
|
310
|
-
if (span.spanType ===
|
|
311
|
-
this.
|
|
236
|
+
if (span.spanType === SpanType.AGENT) {
|
|
237
|
+
this.transport.finishSpan(span.spanId, endTime);
|
|
312
238
|
} else {
|
|
313
|
-
this.
|
|
239
|
+
this.transport.emit(span);
|
|
314
240
|
}
|
|
315
241
|
} catch (error) {
|
|
316
|
-
console.error("Failed to
|
|
242
|
+
console.error("Failed to emit span action:", error);
|
|
317
243
|
}
|
|
318
244
|
}
|
|
319
245
|
async close() {
|
|
320
246
|
try {
|
|
321
|
-
await this.
|
|
247
|
+
await this.transport.close();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error("Failed to close transport:", error);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
startAgentInstance() {
|
|
253
|
+
try {
|
|
254
|
+
this.transport.startAgentInstance();
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error("Failed to start agent instance:", error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
finishAgentInstance() {
|
|
260
|
+
try {
|
|
261
|
+
this.transport.finishAgentInstance();
|
|
322
262
|
} catch (error) {
|
|
323
|
-
console.error("Failed to
|
|
263
|
+
console.error("Failed to finish agent instance:", error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
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
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
await this.onError(error, item);
|
|
386
|
+
} catch {
|
|
387
|
+
return;
|
|
324
388
|
}
|
|
325
389
|
}
|
|
326
390
|
}
|
|
327
391
|
|
|
328
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
|
+
|
|
329
401
|
class Logger {
|
|
330
402
|
namespace;
|
|
331
|
-
static level =
|
|
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
|
+
})();
|
|
332
410
|
constructor(namespace) {
|
|
333
411
|
this.namespace = namespace;
|
|
334
412
|
}
|
|
@@ -372,72 +450,322 @@ function configureLogging() {
|
|
|
372
450
|
}
|
|
373
451
|
}
|
|
374
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
|
+
|
|
375
646
|
// packages/core/src/transport/http.ts
|
|
376
647
|
var logger = getLogger("http-transport");
|
|
377
648
|
|
|
378
649
|
class HttpTransport {
|
|
379
650
|
config;
|
|
380
651
|
closed = false;
|
|
652
|
+
actionQueue = new InMemoryQueue;
|
|
653
|
+
taskExecutor;
|
|
654
|
+
agentInstanceClient;
|
|
655
|
+
agentSpanClient;
|
|
656
|
+
previousAgentSchema = null;
|
|
657
|
+
requiresNewAgentIdentifier = false;
|
|
658
|
+
previousAgentIdentifier = null;
|
|
381
659
|
agentInstanceId = null;
|
|
382
660
|
spanIdMap = new Map;
|
|
383
661
|
pendingFinishes = new Map;
|
|
384
662
|
constructor(config) {
|
|
385
663
|
this.config = config;
|
|
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" });
|
|
386
683
|
}
|
|
387
|
-
|
|
388
|
-
|
|
684
|
+
emit(span) {
|
|
685
|
+
this.enqueue({ type: "span_end", span });
|
|
686
|
+
}
|
|
687
|
+
finishSpan(spanId, endTime) {
|
|
688
|
+
this.enqueue({ type: "span_finish", spanId, endTime });
|
|
689
|
+
}
|
|
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();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
enqueue(action) {
|
|
699
|
+
if (this.closed) {
|
|
389
700
|
return;
|
|
390
701
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
702
|
+
this.actionQueue.put(action).catch((error) => {
|
|
703
|
+
logger.error("Failed to enqueue HTTP action:", error);
|
|
704
|
+
});
|
|
705
|
+
}
|
|
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;
|
|
397
718
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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;
|
|
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;
|
|
431
725
|
}
|
|
726
|
+
this.requiresNewAgentIdentifier = false;
|
|
727
|
+
this.previousAgentIdentifier = null;
|
|
432
728
|
}
|
|
433
|
-
|
|
434
|
-
|
|
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) {
|
|
747
|
+
await this.ensureAgentRegistered();
|
|
748
|
+
}
|
|
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);
|
|
758
|
+
}
|
|
759
|
+
return;
|
|
435
760
|
}
|
|
436
761
|
}
|
|
437
|
-
}
|
|
762
|
+
};
|
|
438
763
|
async processPendingFinishes(spanId) {
|
|
764
|
+
if (!this.pendingFinishes.has(spanId)) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
439
767
|
const pendingEndTime = this.pendingFinishes.get(spanId);
|
|
440
|
-
if (
|
|
768
|
+
if (pendingEndTime === undefined) {
|
|
441
769
|
return;
|
|
442
770
|
}
|
|
443
771
|
try {
|
|
@@ -448,56 +776,32 @@ class HttpTransport {
|
|
|
448
776
|
logger.error("Error processing pending span finish:", error);
|
|
449
777
|
}
|
|
450
778
|
}
|
|
451
|
-
async sendSpan(span
|
|
452
|
-
const url = `${this.config.apiUrl}/api/v1/agent_spans`;
|
|
779
|
+
async sendSpan(span) {
|
|
453
780
|
const payload = this.transformSpanToApiFormat(span);
|
|
454
781
|
try {
|
|
455
|
-
const response = await
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
}
|
|
782
|
+
const response = await this.agentSpanClient.create(payload);
|
|
783
|
+
const backendSpanId = response.details?.id;
|
|
784
|
+
if (!backendSpanId) {
|
|
471
785
|
return;
|
|
472
786
|
}
|
|
473
|
-
|
|
474
|
-
|
|
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}`);
|
|
787
|
+
this.spanIdMap.set(span.spanId, backendSpanId);
|
|
788
|
+
await this.processPendingFinishes(span.spanId);
|
|
480
789
|
} catch (error) {
|
|
481
790
|
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
|
-
}
|
|
487
791
|
}
|
|
488
792
|
}
|
|
489
793
|
transformSpanToApiFormat(span) {
|
|
490
794
|
const startedAt = new Date(span.startTime).toISOString();
|
|
491
795
|
const finishedAt = span.endTime ? new Date(span.endTime).toISOString() : null;
|
|
796
|
+
const apiStatus = this.mapStatusForApi(span.status);
|
|
492
797
|
const payload = {
|
|
493
798
|
span_id: span.spanId,
|
|
494
799
|
trace_id: span.traceId,
|
|
495
800
|
name: span.name,
|
|
496
|
-
status:
|
|
801
|
+
status: apiStatus,
|
|
497
802
|
inputs: span.inputs,
|
|
498
803
|
outputs: span.outputs,
|
|
499
804
|
metadata: span.metadata,
|
|
500
|
-
tags: span.tags,
|
|
501
805
|
token_usage: null,
|
|
502
806
|
error: null
|
|
503
807
|
};
|
|
@@ -520,6 +824,7 @@ class HttpTransport {
|
|
|
520
824
|
details: {
|
|
521
825
|
agent_instance_id: this.agentInstanceId,
|
|
522
826
|
schema_name: span.spanType,
|
|
827
|
+
status: apiStatus,
|
|
523
828
|
payload,
|
|
524
829
|
parent_span_id: parentSpanId,
|
|
525
830
|
started_at: startedAt,
|
|
@@ -527,14 +832,22 @@ class HttpTransport {
|
|
|
527
832
|
}
|
|
528
833
|
};
|
|
529
834
|
}
|
|
530
|
-
|
|
531
|
-
|
|
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
|
+
}
|
|
532
846
|
}
|
|
533
847
|
async ensureAgentRegistered() {
|
|
534
848
|
if (this.agentInstanceId) {
|
|
535
|
-
return;
|
|
849
|
+
return true;
|
|
536
850
|
}
|
|
537
|
-
const url = `${this.config.apiUrl}/api/v1/agent_instance/register`;
|
|
538
851
|
const payload = {};
|
|
539
852
|
if (this.config.agentId)
|
|
540
853
|
payload.agent_id = this.config.agentId;
|
|
@@ -545,61 +858,25 @@ class HttpTransport {
|
|
|
545
858
|
description: this.config.agentDescription || ""
|
|
546
859
|
};
|
|
547
860
|
}
|
|
548
|
-
if (this.config.
|
|
549
|
-
logger.debug("Skipping schema in registration (skipSchema=true)");
|
|
550
|
-
} else if (this.config.agentSchema) {
|
|
551
|
-
logger.debug("Using custom agent schema");
|
|
861
|
+
if (this.config.agentSchema) {
|
|
552
862
|
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
863
|
}
|
|
562
864
|
try {
|
|
563
|
-
const
|
|
564
|
-
|
|
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
|
-
}
|
|
865
|
+
const data = await this.agentInstanceClient.register(payload);
|
|
866
|
+
this.agentInstanceId = data.details?.id ?? null;
|
|
579
867
|
} catch (error) {
|
|
580
868
|
logger.error("Error registering agent:", error);
|
|
581
869
|
}
|
|
870
|
+
return this.agentInstanceId !== null;
|
|
582
871
|
}
|
|
583
872
|
async startAgentInstanceHttp() {
|
|
584
|
-
await this.ensureAgentRegistered();
|
|
585
|
-
if (!this.agentInstanceId) {
|
|
873
|
+
const isRegistered = await this.ensureAgentRegistered();
|
|
874
|
+
if (!isRegistered || !this.agentInstanceId) {
|
|
586
875
|
logger.error("Cannot start agent instance: not registered");
|
|
587
876
|
return;
|
|
588
877
|
}
|
|
589
|
-
const url = `${this.config.apiUrl}/api/v1/agent_instance/${this.agentInstanceId}/start`;
|
|
590
878
|
try {
|
|
591
|
-
|
|
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
|
-
}
|
|
879
|
+
await this.agentInstanceClient.start(this.agentInstanceId);
|
|
603
880
|
} catch (error) {
|
|
604
881
|
logger.error("Error starting agent instance:", error);
|
|
605
882
|
}
|
|
@@ -609,20 +886,8 @@ class HttpTransport {
|
|
|
609
886
|
logger.error("Cannot finish agent instance: not registered");
|
|
610
887
|
return;
|
|
611
888
|
}
|
|
612
|
-
const url = `${this.config.apiUrl}/api/v1/agent_instance/${this.agentInstanceId}/finish`;
|
|
613
889
|
try {
|
|
614
|
-
|
|
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
|
-
}
|
|
890
|
+
await this.agentInstanceClient.finish(this.agentInstanceId);
|
|
626
891
|
} catch (error) {
|
|
627
892
|
logger.error("Error finishing agent instance:", error);
|
|
628
893
|
}
|
|
@@ -634,33 +899,39 @@ class HttpTransport {
|
|
|
634
899
|
logger.warn(`Cannot finish span ${data.spanId}: backend ID not found`);
|
|
635
900
|
return;
|
|
636
901
|
}
|
|
637
|
-
const url = `${this.config.apiUrl}/api/v1/agent_spans/${backendSpanId}/finish`;
|
|
638
902
|
try {
|
|
639
|
-
|
|
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
|
-
}
|
|
903
|
+
await this.agentSpanClient.finish(backendSpanId, data.timestamp);
|
|
651
904
|
} catch (error) {
|
|
652
905
|
logger.error("Error finishing span:", error);
|
|
653
906
|
}
|
|
654
907
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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;
|
|
660
923
|
}
|
|
661
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 };
|
|
662
934
|
}
|
|
663
|
-
|
|
664
935
|
// packages/core/src/utils/serialization.ts
|
|
665
936
|
function truncateString(value, maxLength) {
|
|
666
937
|
if (value.length <= maxLength) {
|
|
@@ -694,153 +965,6 @@ function serializeValue(value, maxLength = 1e4) {
|
|
|
694
965
|
return `<${typeof value} object>`;
|
|
695
966
|
}
|
|
696
967
|
}
|
|
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
968
|
export {
|
|
845
969
|
truncateString,
|
|
846
970
|
serializeValue,
|
|
@@ -849,18 +973,14 @@ export {
|
|
|
849
973
|
createConfig,
|
|
850
974
|
configureLogging,
|
|
851
975
|
Tracer,
|
|
852
|
-
StdioTransport,
|
|
853
976
|
SpanType,
|
|
854
977
|
SpanStatus,
|
|
855
978
|
SpanContext,
|
|
856
|
-
SchemaRegistry,
|
|
857
979
|
PartialHttpConfigSchema,
|
|
858
|
-
InMemoryQueue,
|
|
859
980
|
HttpTransportConfigSchema,
|
|
860
981
|
HttpTransport,
|
|
861
|
-
DEFAULT_AGENT_SCHEMA,
|
|
862
982
|
ConfigSchema,
|
|
863
983
|
AgentInstanceManager
|
|
864
984
|
};
|
|
865
985
|
|
|
866
|
-
//# debugId=
|
|
986
|
+
//# debugId=ED42D0431E54CF4064756E2164756E21
|