@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.cjs
CHANGED
|
@@ -36,108 +36,74 @@ __export(exports_src, {
|
|
|
36
36
|
createConfig: () => createConfig,
|
|
37
37
|
configureLogging: () => configureLogging,
|
|
38
38
|
Tracer: () => Tracer,
|
|
39
|
-
StdioTransport: () => StdioTransport,
|
|
40
39
|
SpanType: () => SpanType,
|
|
41
40
|
SpanStatus: () => SpanStatus,
|
|
42
41
|
SpanContext: () => SpanContext,
|
|
43
|
-
SchemaRegistry: () => SchemaRegistry,
|
|
44
42
|
PartialHttpConfigSchema: () => PartialHttpConfigSchema,
|
|
45
|
-
InMemoryQueue: () => InMemoryQueue,
|
|
46
43
|
HttpTransportConfigSchema: () => HttpTransportConfigSchema,
|
|
47
44
|
HttpTransport: () => HttpTransport,
|
|
48
|
-
DEFAULT_AGENT_SCHEMA: () => DEFAULT_AGENT_SCHEMA,
|
|
49
45
|
ConfigSchema: () => ConfigSchema,
|
|
50
46
|
AgentInstanceManager: () => AgentInstanceManager
|
|
51
47
|
});
|
|
52
48
|
module.exports = __toCommonJS(exports_src);
|
|
53
49
|
|
|
54
50
|
// packages/core/src/agent/instance-manager.ts
|
|
55
|
-
var import_node_util = require("node:util");
|
|
56
|
-
|
|
57
|
-
// packages/core/src/agent/schema-registry.ts
|
|
58
|
-
class SchemaRegistry {
|
|
59
|
-
schemas = new Map;
|
|
60
|
-
register(schema) {
|
|
61
|
-
this.schemas.set(this.getKey(schema.schemaName, schema.schemaIdentifier), schema);
|
|
62
|
-
}
|
|
63
|
-
has(schemaName, schemaIdentifier) {
|
|
64
|
-
return this.schemas.has(this.getKey(schemaName, schemaIdentifier));
|
|
65
|
-
}
|
|
66
|
-
get(schemaName, schemaIdentifier) {
|
|
67
|
-
return this.schemas.get(this.getKey(schemaName, schemaIdentifier));
|
|
68
|
-
}
|
|
69
|
-
getKey(schemaName, schemaIdentifier) {
|
|
70
|
-
return `${schemaName}@${schemaIdentifier}`;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// packages/core/src/agent/instance-manager.ts
|
|
75
|
-
var orderInsensitiveArrayKeys = new Set(["required", "enum", "oneOf", "allOf", "anyOf", "type"]);
|
|
76
|
-
var stableStringify = (value) => JSON.stringify(value) ?? "undefined";
|
|
77
|
-
var normalizeSchema = (value, arrayKey) => {
|
|
78
|
-
if (Array.isArray(value)) {
|
|
79
|
-
const normalizedItems = value.map((item) => normalizeSchema(item));
|
|
80
|
-
if (arrayKey && orderInsensitiveArrayKeys.has(arrayKey)) {
|
|
81
|
-
return normalizedItems.map((item) => ({ item, key: stableStringify(item) })).sort((left, right) => left.key.localeCompare(right.key)).map(({ item }) => item);
|
|
82
|
-
}
|
|
83
|
-
return normalizedItems;
|
|
84
|
-
}
|
|
85
|
-
if (value && typeof value === "object") {
|
|
86
|
-
const proto = Object.getPrototypeOf(value);
|
|
87
|
-
if (proto === Object.prototype || proto === null) {
|
|
88
|
-
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
89
|
-
const normalized = {};
|
|
90
|
-
for (const [key, entryValue] of entries) {
|
|
91
|
-
normalized[key] = normalizeSchema(entryValue, key);
|
|
92
|
-
}
|
|
93
|
-
return normalized;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return value;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
51
|
class AgentInstanceManager {
|
|
100
|
-
|
|
52
|
+
transport;
|
|
101
53
|
options;
|
|
102
|
-
|
|
103
|
-
constructor(
|
|
104
|
-
this.
|
|
54
|
+
registeredSchema = null;
|
|
55
|
+
constructor(transport, options) {
|
|
56
|
+
this.transport = transport;
|
|
105
57
|
this.options = options;
|
|
106
58
|
}
|
|
107
59
|
registerSchema(schema) {
|
|
108
|
-
if (this.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
console.warn(`Schema ${this.options.schemaName}@${this.options.schemaIdentifier} is already registered with a different payload. Ignoring registration.`);
|
|
112
|
-
}
|
|
60
|
+
if (this.registeredSchema === null) {
|
|
61
|
+
this.registeredSchema = schema;
|
|
62
|
+
this.transport.registerSchema(schema);
|
|
113
63
|
return;
|
|
114
64
|
}
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
schema
|
|
119
|
-
}
|
|
120
|
-
this.schemaRegistry.register(registration);
|
|
121
|
-
this.queue.enqueue({ type: "schema_register", data: registration });
|
|
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
|
+
}
|
|
122
70
|
}
|
|
123
71
|
startInstance(options = {}) {
|
|
124
|
-
if (!this.options.allowUnregisteredSchema &&
|
|
125
|
-
console.warn(
|
|
72
|
+
if (!this.options.allowUnregisteredSchema && this.registeredSchema === null) {
|
|
73
|
+
console.warn("Schema must be registered before starting an agent instance.");
|
|
126
74
|
return;
|
|
127
75
|
}
|
|
128
|
-
|
|
129
|
-
...options,
|
|
130
|
-
schemaName: this.options.schemaName,
|
|
131
|
-
schemaIdentifier: this.options.schemaIdentifier
|
|
132
|
-
};
|
|
133
|
-
this.queue.enqueue({ type: "agent_start", data: startData });
|
|
76
|
+
this.transport.startAgentInstance(options);
|
|
134
77
|
}
|
|
135
78
|
finishInstance() {
|
|
136
|
-
this.
|
|
79
|
+
this.transport.finishAgentInstance();
|
|
137
80
|
}
|
|
138
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
|
+
}
|
|
139
100
|
// packages/core/src/config.ts
|
|
140
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);
|
|
141
107
|
var HttpTransportConfigSchema = import_zod.z.object({
|
|
142
108
|
apiUrl: import_zod.z.string().url(),
|
|
143
109
|
apiToken: import_zod.z.string().min(1),
|
|
@@ -145,17 +111,13 @@ var HttpTransportConfigSchema = import_zod.z.object({
|
|
|
145
111
|
agentIdentifier: import_zod.z.string().default("v1.0.0"),
|
|
146
112
|
agentName: import_zod.z.string().optional(),
|
|
147
113
|
agentDescription: import_zod.z.string().optional(),
|
|
148
|
-
schemaName: import_zod.z.string().optional(),
|
|
149
|
-
schemaIdentifier: import_zod.z.string().optional(),
|
|
150
114
|
agentSchema: import_zod.z.record(import_zod.z.unknown()).optional(),
|
|
151
|
-
agentSchemaIdentifier: import_zod.z.string().optional(),
|
|
152
|
-
skipSchema: import_zod.z.boolean().default(false),
|
|
153
115
|
requestTimeout: import_zod.z.number().positive().default(30000),
|
|
154
|
-
connectTimeout: import_zod.z.number().positive().default(1e4),
|
|
155
116
|
maxRetries: import_zod.z.number().int().nonnegative().default(3),
|
|
156
117
|
initialRetryDelay: import_zod.z.number().positive().default(1000),
|
|
157
118
|
maxRetryDelay: import_zod.z.number().positive().default(60000),
|
|
158
|
-
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])
|
|
159
121
|
});
|
|
160
122
|
var PartialHttpConfigSchema = import_zod.z.object({
|
|
161
123
|
apiUrl: import_zod.z.string().url(),
|
|
@@ -164,20 +126,16 @@ var PartialHttpConfigSchema = import_zod.z.object({
|
|
|
164
126
|
agentIdentifier: import_zod.z.string().optional(),
|
|
165
127
|
agentName: import_zod.z.string().optional(),
|
|
166
128
|
agentDescription: import_zod.z.string().optional(),
|
|
167
|
-
schemaName: import_zod.z.string().optional(),
|
|
168
|
-
schemaIdentifier: import_zod.z.string().optional(),
|
|
169
129
|
agentSchema: import_zod.z.record(import_zod.z.unknown()).optional(),
|
|
170
|
-
agentSchemaIdentifier: import_zod.z.string().optional(),
|
|
171
|
-
skipSchema: import_zod.z.boolean().optional(),
|
|
172
130
|
requestTimeout: import_zod.z.number().positive().optional(),
|
|
173
|
-
connectTimeout: import_zod.z.number().positive().optional(),
|
|
174
131
|
maxRetries: import_zod.z.number().int().nonnegative().optional(),
|
|
175
132
|
initialRetryDelay: import_zod.z.number().positive().optional(),
|
|
176
133
|
maxRetryDelay: import_zod.z.number().positive().optional(),
|
|
177
|
-
retryMultiplier: import_zod.z.number().positive().optional()
|
|
134
|
+
retryMultiplier: import_zod.z.number().positive().optional(),
|
|
135
|
+
retryOnStatusCodes: import_zod.z.array(HttpStatusCodeSchema).optional()
|
|
178
136
|
});
|
|
179
137
|
var ConfigSchema = import_zod.z.object({
|
|
180
|
-
transportType: import_zod.z.enum(["
|
|
138
|
+
transportType: import_zod.z.enum(["http"]).default("http"),
|
|
181
139
|
sampleRate: import_zod.z.number().min(0).max(1).default(1),
|
|
182
140
|
captureInputs: import_zod.z.boolean().default(true),
|
|
183
141
|
captureOutputs: import_zod.z.boolean().default(true),
|
|
@@ -186,38 +144,30 @@ var ConfigSchema = import_zod.z.object({
|
|
|
186
144
|
httpConfig: PartialHttpConfigSchema.optional()
|
|
187
145
|
});
|
|
188
146
|
function createConfig(options) {
|
|
147
|
+
const retryOnStatusCodesFromEnv = parseRetryOnStatusCodesEnv(process.env.PREFACTOR_RETRY_ON_STATUS_CODES);
|
|
189
148
|
const config = {
|
|
190
|
-
transportType: options?.transportType ?? process.env.PREFACTOR_TRANSPORT ?? "
|
|
149
|
+
transportType: options?.transportType ?? process.env.PREFACTOR_TRANSPORT ?? "http",
|
|
191
150
|
sampleRate: options?.sampleRate ?? parseFloat(process.env.PREFACTOR_SAMPLE_RATE ?? "1.0"),
|
|
192
151
|
captureInputs: options?.captureInputs ?? process.env.PREFACTOR_CAPTURE_INPUTS !== "false",
|
|
193
152
|
captureOutputs: options?.captureOutputs ?? process.env.PREFACTOR_CAPTURE_OUTPUTS !== "false",
|
|
194
153
|
maxInputLength: options?.maxInputLength ?? parseInt(process.env.PREFACTOR_MAX_INPUT_LENGTH ?? "10000", 10),
|
|
195
154
|
maxOutputLength: options?.maxOutputLength ?? parseInt(process.env.PREFACTOR_MAX_OUTPUT_LENGTH ?? "10000", 10),
|
|
196
|
-
httpConfig: options?.httpConfig
|
|
155
|
+
httpConfig: options?.httpConfig ? {
|
|
156
|
+
...options.httpConfig,
|
|
157
|
+
retryOnStatusCodes: options.httpConfig.retryOnStatusCodes ?? retryOnStatusCodesFromEnv
|
|
158
|
+
} : undefined
|
|
197
159
|
};
|
|
198
160
|
return ConfigSchema.parse(config);
|
|
199
161
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// packages/core/src/queue/in-memory.ts
|
|
204
|
-
class InMemoryQueue {
|
|
205
|
-
items = [];
|
|
206
|
-
enqueue(item) {
|
|
207
|
-
this.items.push(item);
|
|
208
|
-
}
|
|
209
|
-
dequeueBatch(maxItems) {
|
|
210
|
-
if (this.items.length === 0)
|
|
211
|
-
return [];
|
|
212
|
-
return this.items.splice(0, maxItems);
|
|
213
|
-
}
|
|
214
|
-
size() {
|
|
215
|
-
return this.items.length;
|
|
216
|
-
}
|
|
217
|
-
async flush() {
|
|
162
|
+
function parseRetryOnStatusCodesEnv(value) {
|
|
163
|
+
if (!value) {
|
|
218
164
|
return;
|
|
219
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;
|
|
220
168
|
}
|
|
169
|
+
// packages/core/src/create-core.ts
|
|
170
|
+
var import_pfid2 = require("@prefactor/pfid");
|
|
221
171
|
|
|
222
172
|
// packages/core/src/tracing/tracer.ts
|
|
223
173
|
var import_pfid = require("@prefactor/pfid");
|
|
@@ -267,38 +217,11 @@ class SpanContext {
|
|
|
267
217
|
}
|
|
268
218
|
|
|
269
219
|
// packages/core/src/tracing/span.ts
|
|
270
|
-
var SpanType
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
SpanType2["CHAIN"] = "chain";
|
|
276
|
-
SpanType2["RETRIEVER"] = "retriever";
|
|
277
|
-
})(SpanType ||= {});
|
|
278
|
-
var DEFAULT_AGENT_SCHEMA = {
|
|
279
|
-
external_identifier: "1.0.0",
|
|
280
|
-
span_schemas: {
|
|
281
|
-
agent: {
|
|
282
|
-
type: "object",
|
|
283
|
-
properties: { type: { type: "string", const: "agent" } }
|
|
284
|
-
},
|
|
285
|
-
llm: {
|
|
286
|
-
type: "object",
|
|
287
|
-
properties: { type: { type: "string", const: "llm" } }
|
|
288
|
-
},
|
|
289
|
-
tool: {
|
|
290
|
-
type: "object",
|
|
291
|
-
properties: { type: { type: "string", const: "tool" } }
|
|
292
|
-
},
|
|
293
|
-
chain: {
|
|
294
|
-
type: "object",
|
|
295
|
-
properties: { type: { type: "string", const: "chain" } }
|
|
296
|
-
},
|
|
297
|
-
retriever: {
|
|
298
|
-
type: "object",
|
|
299
|
-
properties: { type: { type: "string", const: "retriever" } }
|
|
300
|
-
}
|
|
301
|
-
}
|
|
220
|
+
var SpanType = {
|
|
221
|
+
AGENT: "agent",
|
|
222
|
+
LLM: "llm",
|
|
223
|
+
TOOL: "tool",
|
|
224
|
+
CHAIN: "chain"
|
|
302
225
|
};
|
|
303
226
|
var SpanStatus;
|
|
304
227
|
((SpanStatus2) => {
|
|
@@ -309,10 +232,10 @@ var SpanStatus;
|
|
|
309
232
|
|
|
310
233
|
// packages/core/src/tracing/tracer.ts
|
|
311
234
|
class Tracer {
|
|
312
|
-
|
|
235
|
+
transport;
|
|
313
236
|
partition;
|
|
314
|
-
constructor(
|
|
315
|
-
this.
|
|
237
|
+
constructor(transport, partition) {
|
|
238
|
+
this.transport = transport;
|
|
316
239
|
this.partition = partition ?? import_pfid.generatePartition();
|
|
317
240
|
}
|
|
318
241
|
startSpan(options) {
|
|
@@ -332,14 +255,13 @@ class Tracer {
|
|
|
332
255
|
outputs: null,
|
|
333
256
|
tokenUsage: null,
|
|
334
257
|
error: null,
|
|
335
|
-
metadata: options.metadata ?? {}
|
|
336
|
-
tags: options.tags ?? []
|
|
258
|
+
metadata: options.metadata ?? {}
|
|
337
259
|
};
|
|
338
|
-
if (options.spanType ===
|
|
260
|
+
if (options.spanType === SpanType.AGENT) {
|
|
339
261
|
try {
|
|
340
|
-
this.
|
|
262
|
+
this.transport.emit(span);
|
|
341
263
|
} catch (error) {
|
|
342
|
-
console.error("Failed to
|
|
264
|
+
console.error("Failed to emit agent span:", error);
|
|
343
265
|
}
|
|
344
266
|
}
|
|
345
267
|
return span;
|
|
@@ -360,28 +282,180 @@ class Tracer {
|
|
|
360
282
|
span.status = "success" /* SUCCESS */;
|
|
361
283
|
}
|
|
362
284
|
try {
|
|
363
|
-
if (span.spanType ===
|
|
364
|
-
this.
|
|
285
|
+
if (span.spanType === SpanType.AGENT) {
|
|
286
|
+
this.transport.finishSpan(span.spanId, endTime);
|
|
365
287
|
} else {
|
|
366
|
-
this.
|
|
288
|
+
this.transport.emit(span);
|
|
367
289
|
}
|
|
368
290
|
} catch (error) {
|
|
369
|
-
console.error("Failed to
|
|
291
|
+
console.error("Failed to emit span action:", error);
|
|
370
292
|
}
|
|
371
293
|
}
|
|
372
294
|
async close() {
|
|
373
295
|
try {
|
|
374
|
-
await this.
|
|
296
|
+
await this.transport.close();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error("Failed to close transport:", error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
startAgentInstance() {
|
|
302
|
+
try {
|
|
303
|
+
this.transport.startAgentInstance();
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error("Failed to start agent instance:", error);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
finishAgentInstance() {
|
|
309
|
+
try {
|
|
310
|
+
this.transport.finishAgentInstance();
|
|
375
311
|
} catch (error) {
|
|
376
|
-
console.error("Failed to
|
|
312
|
+
console.error("Failed to finish agent instance:", error);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
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
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
await this.onError(error, item);
|
|
435
|
+
} catch {
|
|
436
|
+
return;
|
|
377
437
|
}
|
|
378
438
|
}
|
|
379
439
|
}
|
|
380
440
|
|
|
381
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
|
+
|
|
382
450
|
class Logger {
|
|
383
451
|
namespace;
|
|
384
|
-
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
|
+
})();
|
|
385
459
|
constructor(namespace) {
|
|
386
460
|
this.namespace = namespace;
|
|
387
461
|
}
|
|
@@ -425,72 +499,322 @@ function configureLogging() {
|
|
|
425
499
|
}
|
|
426
500
|
}
|
|
427
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
|
+
|
|
428
695
|
// packages/core/src/transport/http.ts
|
|
429
696
|
var logger = getLogger("http-transport");
|
|
430
697
|
|
|
431
698
|
class HttpTransport {
|
|
432
699
|
config;
|
|
433
700
|
closed = false;
|
|
701
|
+
actionQueue = new InMemoryQueue;
|
|
702
|
+
taskExecutor;
|
|
703
|
+
agentInstanceClient;
|
|
704
|
+
agentSpanClient;
|
|
705
|
+
previousAgentSchema = null;
|
|
706
|
+
requiresNewAgentIdentifier = false;
|
|
707
|
+
previousAgentIdentifier = null;
|
|
434
708
|
agentInstanceId = null;
|
|
435
709
|
spanIdMap = new Map;
|
|
436
710
|
pendingFinishes = new Map;
|
|
437
711
|
constructor(config) {
|
|
438
712
|
this.config = config;
|
|
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" });
|
|
439
732
|
}
|
|
440
|
-
|
|
441
|
-
|
|
733
|
+
emit(span) {
|
|
734
|
+
this.enqueue({ type: "span_end", span });
|
|
735
|
+
}
|
|
736
|
+
finishSpan(spanId, endTime) {
|
|
737
|
+
this.enqueue({ type: "span_finish", spanId, endTime });
|
|
738
|
+
}
|
|
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();
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
enqueue(action) {
|
|
748
|
+
if (this.closed) {
|
|
442
749
|
return;
|
|
443
750
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
751
|
+
this.actionQueue.put(action).catch((error) => {
|
|
752
|
+
logger.error("Failed to enqueue HTTP action:", error);
|
|
753
|
+
});
|
|
754
|
+
}
|
|
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;
|
|
450
767
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
case "agent_start":
|
|
458
|
-
this.config.agentId = item.data.agentId;
|
|
459
|
-
if (item.data.agentIdentifier !== undefined)
|
|
460
|
-
this.config.agentIdentifier = item.data.agentIdentifier;
|
|
461
|
-
this.config.agentName = item.data.agentName;
|
|
462
|
-
this.config.agentDescription = item.data.agentDescription;
|
|
463
|
-
await this.startAgentInstanceHttp();
|
|
464
|
-
break;
|
|
465
|
-
case "agent_finish":
|
|
466
|
-
await this.finishAgentInstanceHttp();
|
|
467
|
-
break;
|
|
468
|
-
case "span_end":
|
|
469
|
-
if (!this.agentInstanceId) {
|
|
470
|
-
await this.ensureAgentRegistered();
|
|
471
|
-
}
|
|
472
|
-
await this.sendSpan(item.data);
|
|
473
|
-
break;
|
|
474
|
-
case "span_finish": {
|
|
475
|
-
const spanId = item.data.spanId;
|
|
476
|
-
const backendSpanId = this.spanIdMap.get(spanId);
|
|
477
|
-
if (backendSpanId) {
|
|
478
|
-
const timestamp = new Date(item.data.endTime).toISOString();
|
|
479
|
-
await this.finishSpanHttp({ spanId, timestamp });
|
|
480
|
-
} else {
|
|
481
|
-
this.pendingFinishes.set(spanId, item.data.endTime);
|
|
482
|
-
}
|
|
483
|
-
break;
|
|
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;
|
|
484
774
|
}
|
|
775
|
+
this.requiresNewAgentIdentifier = false;
|
|
776
|
+
this.previousAgentIdentifier = null;
|
|
485
777
|
}
|
|
486
|
-
|
|
487
|
-
|
|
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) {
|
|
796
|
+
await this.ensureAgentRegistered();
|
|
797
|
+
}
|
|
798
|
+
await this.sendSpan(action.span);
|
|
799
|
+
return;
|
|
800
|
+
case "span_finish": {
|
|
801
|
+
const backendSpanId = this.spanIdMap.get(action.spanId);
|
|
802
|
+
if (backendSpanId) {
|
|
803
|
+
const timestamp = new Date(action.endTime).toISOString();
|
|
804
|
+
await this.finishSpanHttp({ spanId: action.spanId, timestamp });
|
|
805
|
+
} else {
|
|
806
|
+
this.pendingFinishes.set(action.spanId, action.endTime);
|
|
807
|
+
}
|
|
808
|
+
return;
|
|
488
809
|
}
|
|
489
810
|
}
|
|
490
|
-
}
|
|
811
|
+
};
|
|
491
812
|
async processPendingFinishes(spanId) {
|
|
813
|
+
if (!this.pendingFinishes.has(spanId)) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
492
816
|
const pendingEndTime = this.pendingFinishes.get(spanId);
|
|
493
|
-
if (
|
|
817
|
+
if (pendingEndTime === undefined) {
|
|
494
818
|
return;
|
|
495
819
|
}
|
|
496
820
|
try {
|
|
@@ -501,56 +825,32 @@ class HttpTransport {
|
|
|
501
825
|
logger.error("Error processing pending span finish:", error);
|
|
502
826
|
}
|
|
503
827
|
}
|
|
504
|
-
async sendSpan(span
|
|
505
|
-
const url = `${this.config.apiUrl}/api/v1/agent_spans`;
|
|
828
|
+
async sendSpan(span) {
|
|
506
829
|
const payload = this.transformSpanToApiFormat(span);
|
|
507
830
|
try {
|
|
508
|
-
const response = await
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
Authorization: `Bearer ${this.config.apiToken}`,
|
|
512
|
-
"Content-Type": "application/json"
|
|
513
|
-
},
|
|
514
|
-
body: JSON.stringify(payload),
|
|
515
|
-
signal: AbortSignal.timeout(this.config.requestTimeout)
|
|
516
|
-
});
|
|
517
|
-
if (response.ok) {
|
|
518
|
-
const data = await response.json();
|
|
519
|
-
const backendSpanId = data?.details?.id;
|
|
520
|
-
if (backendSpanId) {
|
|
521
|
-
this.spanIdMap.set(span.spanId, backendSpanId);
|
|
522
|
-
await this.processPendingFinishes(span.spanId);
|
|
523
|
-
}
|
|
831
|
+
const response = await this.agentSpanClient.create(payload);
|
|
832
|
+
const backendSpanId = response.details?.id;
|
|
833
|
+
if (!backendSpanId) {
|
|
524
834
|
return;
|
|
525
835
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
logger.debug(`Retrying span send after ${delay}ms (attempt ${retry + 1})`);
|
|
529
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
530
|
-
return this.sendSpan(span, retry + 1);
|
|
531
|
-
}
|
|
532
|
-
logger.error(`Failed to send span: ${response.status} ${response.statusText}`);
|
|
836
|
+
this.spanIdMap.set(span.spanId, backendSpanId);
|
|
837
|
+
await this.processPendingFinishes(span.spanId);
|
|
533
838
|
} catch (error) {
|
|
534
839
|
logger.error("Error sending span:", error);
|
|
535
|
-
if (retry < this.config.maxRetries) {
|
|
536
|
-
const delay = Math.min(this.config.initialRetryDelay * this.config.retryMultiplier ** retry, this.config.maxRetryDelay);
|
|
537
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
538
|
-
return this.sendSpan(span, retry + 1);
|
|
539
|
-
}
|
|
540
840
|
}
|
|
541
841
|
}
|
|
542
842
|
transformSpanToApiFormat(span) {
|
|
543
843
|
const startedAt = new Date(span.startTime).toISOString();
|
|
544
844
|
const finishedAt = span.endTime ? new Date(span.endTime).toISOString() : null;
|
|
845
|
+
const apiStatus = this.mapStatusForApi(span.status);
|
|
545
846
|
const payload = {
|
|
546
847
|
span_id: span.spanId,
|
|
547
848
|
trace_id: span.traceId,
|
|
548
849
|
name: span.name,
|
|
549
|
-
status:
|
|
850
|
+
status: apiStatus,
|
|
550
851
|
inputs: span.inputs,
|
|
551
852
|
outputs: span.outputs,
|
|
552
853
|
metadata: span.metadata,
|
|
553
|
-
tags: span.tags,
|
|
554
854
|
token_usage: null,
|
|
555
855
|
error: null
|
|
556
856
|
};
|
|
@@ -573,6 +873,7 @@ class HttpTransport {
|
|
|
573
873
|
details: {
|
|
574
874
|
agent_instance_id: this.agentInstanceId,
|
|
575
875
|
schema_name: span.spanType,
|
|
876
|
+
status: apiStatus,
|
|
576
877
|
payload,
|
|
577
878
|
parent_span_id: parentSpanId,
|
|
578
879
|
started_at: startedAt,
|
|
@@ -580,14 +881,22 @@ class HttpTransport {
|
|
|
580
881
|
}
|
|
581
882
|
};
|
|
582
883
|
}
|
|
583
|
-
|
|
584
|
-
|
|
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
|
+
}
|
|
585
895
|
}
|
|
586
896
|
async ensureAgentRegistered() {
|
|
587
897
|
if (this.agentInstanceId) {
|
|
588
|
-
return;
|
|
898
|
+
return true;
|
|
589
899
|
}
|
|
590
|
-
const url = `${this.config.apiUrl}/api/v1/agent_instance/register`;
|
|
591
900
|
const payload = {};
|
|
592
901
|
if (this.config.agentId)
|
|
593
902
|
payload.agent_id = this.config.agentId;
|
|
@@ -598,61 +907,25 @@ class HttpTransport {
|
|
|
598
907
|
description: this.config.agentDescription || ""
|
|
599
908
|
};
|
|
600
909
|
}
|
|
601
|
-
if (this.config.
|
|
602
|
-
logger.debug("Skipping schema in registration (skipSchema=true)");
|
|
603
|
-
} else if (this.config.agentSchema) {
|
|
604
|
-
logger.debug("Using custom agent schema");
|
|
910
|
+
if (this.config.agentSchema) {
|
|
605
911
|
payload.agent_schema_version = this.config.agentSchema;
|
|
606
|
-
} else if (this.config.agentSchemaIdentifier) {
|
|
607
|
-
logger.debug(`Using schema version: ${this.config.agentSchemaIdentifier}`);
|
|
608
|
-
payload.agent_schema_version = {
|
|
609
|
-
external_identifier: this.config.agentSchemaIdentifier
|
|
610
|
-
};
|
|
611
|
-
} else {
|
|
612
|
-
logger.debug("Using default hardcoded schema (v1.0.0)");
|
|
613
|
-
payload.agent_schema_version = this.getDefaultSchema();
|
|
614
912
|
}
|
|
615
913
|
try {
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
headers: {
|
|
619
|
-
Authorization: `Bearer ${this.config.apiToken}`,
|
|
620
|
-
"Content-Type": "application/json"
|
|
621
|
-
},
|
|
622
|
-
body: JSON.stringify(payload),
|
|
623
|
-
signal: AbortSignal.timeout(this.config.requestTimeout)
|
|
624
|
-
});
|
|
625
|
-
if (response.ok) {
|
|
626
|
-
const data = await response.json();
|
|
627
|
-
this.agentInstanceId = data?.details?.id ?? null;
|
|
628
|
-
logger.debug(`Registered agent instance: ${this.agentInstanceId}`);
|
|
629
|
-
} else {
|
|
630
|
-
logger.error(`Failed to register agent: ${response.status} ${response.statusText}`);
|
|
631
|
-
}
|
|
914
|
+
const data = await this.agentInstanceClient.register(payload);
|
|
915
|
+
this.agentInstanceId = data.details?.id ?? null;
|
|
632
916
|
} catch (error) {
|
|
633
917
|
logger.error("Error registering agent:", error);
|
|
634
918
|
}
|
|
919
|
+
return this.agentInstanceId !== null;
|
|
635
920
|
}
|
|
636
921
|
async startAgentInstanceHttp() {
|
|
637
|
-
await this.ensureAgentRegistered();
|
|
638
|
-
if (!this.agentInstanceId) {
|
|
922
|
+
const isRegistered = await this.ensureAgentRegistered();
|
|
923
|
+
if (!isRegistered || !this.agentInstanceId) {
|
|
639
924
|
logger.error("Cannot start agent instance: not registered");
|
|
640
925
|
return;
|
|
641
926
|
}
|
|
642
|
-
const url = `${this.config.apiUrl}/api/v1/agent_instance/${this.agentInstanceId}/start`;
|
|
643
927
|
try {
|
|
644
|
-
|
|
645
|
-
method: "POST",
|
|
646
|
-
headers: {
|
|
647
|
-
Authorization: `Bearer ${this.config.apiToken}`,
|
|
648
|
-
"Content-Type": "application/json"
|
|
649
|
-
},
|
|
650
|
-
body: JSON.stringify({}),
|
|
651
|
-
signal: AbortSignal.timeout(this.config.requestTimeout)
|
|
652
|
-
});
|
|
653
|
-
if (!response.ok) {
|
|
654
|
-
logger.error(`Failed to start agent instance: ${response.status} ${response.statusText}`);
|
|
655
|
-
}
|
|
928
|
+
await this.agentInstanceClient.start(this.agentInstanceId);
|
|
656
929
|
} catch (error) {
|
|
657
930
|
logger.error("Error starting agent instance:", error);
|
|
658
931
|
}
|
|
@@ -662,20 +935,8 @@ class HttpTransport {
|
|
|
662
935
|
logger.error("Cannot finish agent instance: not registered");
|
|
663
936
|
return;
|
|
664
937
|
}
|
|
665
|
-
const url = `${this.config.apiUrl}/api/v1/agent_instance/${this.agentInstanceId}/finish`;
|
|
666
938
|
try {
|
|
667
|
-
|
|
668
|
-
method: "POST",
|
|
669
|
-
headers: {
|
|
670
|
-
Authorization: `Bearer ${this.config.apiToken}`,
|
|
671
|
-
"Content-Type": "application/json"
|
|
672
|
-
},
|
|
673
|
-
body: JSON.stringify({}),
|
|
674
|
-
signal: AbortSignal.timeout(this.config.requestTimeout)
|
|
675
|
-
});
|
|
676
|
-
if (!response.ok) {
|
|
677
|
-
logger.error(`Failed to finish agent instance: ${response.status} ${response.statusText}`);
|
|
678
|
-
}
|
|
939
|
+
await this.agentInstanceClient.finish(this.agentInstanceId);
|
|
679
940
|
} catch (error) {
|
|
680
941
|
logger.error("Error finishing agent instance:", error);
|
|
681
942
|
}
|
|
@@ -687,33 +948,39 @@ class HttpTransport {
|
|
|
687
948
|
logger.warn(`Cannot finish span ${data.spanId}: backend ID not found`);
|
|
688
949
|
return;
|
|
689
950
|
}
|
|
690
|
-
const url = `${this.config.apiUrl}/api/v1/agent_spans/${backendSpanId}/finish`;
|
|
691
951
|
try {
|
|
692
|
-
|
|
693
|
-
method: "POST",
|
|
694
|
-
headers: {
|
|
695
|
-
Authorization: `Bearer ${this.config.apiToken}`,
|
|
696
|
-
"Content-Type": "application/json"
|
|
697
|
-
},
|
|
698
|
-
body: JSON.stringify({ timestamp: data.timestamp }),
|
|
699
|
-
signal: AbortSignal.timeout(this.config.requestTimeout)
|
|
700
|
-
});
|
|
701
|
-
if (!response.ok) {
|
|
702
|
-
logger.error(`Failed to finish span: ${response.status} ${response.statusText}`);
|
|
703
|
-
}
|
|
952
|
+
await this.agentSpanClient.finish(backendSpanId, data.timestamp);
|
|
704
953
|
} catch (error) {
|
|
705
954
|
logger.error("Error finishing span:", error);
|
|
706
955
|
}
|
|
707
956
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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;
|
|
713
972
|
}
|
|
714
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 };
|
|
715
983
|
}
|
|
716
|
-
|
|
717
984
|
// packages/core/src/utils/serialization.ts
|
|
718
985
|
function truncateString(value, maxLength) {
|
|
719
986
|
if (value.length <= maxLength) {
|
|
@@ -748,151 +1015,4 @@ function serializeValue(value, maxLength = 1e4) {
|
|
|
748
1015
|
}
|
|
749
1016
|
}
|
|
750
1017
|
|
|
751
|
-
|
|
752
|
-
class StdioTransport {
|
|
753
|
-
closed = false;
|
|
754
|
-
writeLock = Promise.resolve();
|
|
755
|
-
async processBatch(items) {
|
|
756
|
-
if (this.closed || items.length === 0) {
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
this.writeLock = this.writeLock.then(async () => {
|
|
760
|
-
for (const item of items) {
|
|
761
|
-
try {
|
|
762
|
-
const serialized = serializeValue(item);
|
|
763
|
-
const json = JSON.stringify(serialized);
|
|
764
|
-
await Bun.write(Bun.stdout, `${json}
|
|
765
|
-
`);
|
|
766
|
-
} catch (error) {
|
|
767
|
-
console.error("Failed to emit queue action to stdout:", error);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
});
|
|
771
|
-
await this.writeLock;
|
|
772
|
-
}
|
|
773
|
-
async close() {
|
|
774
|
-
this.closed = true;
|
|
775
|
-
await this.writeLock;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// packages/core/src/transport/worker.ts
|
|
780
|
-
class TransportWorker {
|
|
781
|
-
queue;
|
|
782
|
-
transport;
|
|
783
|
-
config;
|
|
784
|
-
closed = false;
|
|
785
|
-
inFlightPromise = null;
|
|
786
|
-
loopPromise;
|
|
787
|
-
pendingBatch = null;
|
|
788
|
-
constructor(queue, transport, config) {
|
|
789
|
-
this.queue = queue;
|
|
790
|
-
this.transport = transport;
|
|
791
|
-
this.config = config;
|
|
792
|
-
this.loopPromise = this.start();
|
|
793
|
-
}
|
|
794
|
-
async start() {
|
|
795
|
-
while (!this.closed || this.pendingBatch || this.queue.size() > 0 || this.inFlightPromise) {
|
|
796
|
-
const batch = this.pendingBatch ?? this.queue.dequeueBatch(this.config.batchSize);
|
|
797
|
-
if (batch.length === 0) {
|
|
798
|
-
await new Promise((resolve) => setTimeout(resolve, this.config.intervalMs));
|
|
799
|
-
continue;
|
|
800
|
-
}
|
|
801
|
-
try {
|
|
802
|
-
const inFlight = this.transport.processBatch(batch);
|
|
803
|
-
this.inFlightPromise = inFlight;
|
|
804
|
-
await inFlight;
|
|
805
|
-
this.pendingBatch = null;
|
|
806
|
-
} catch (error) {
|
|
807
|
-
this.pendingBatch = batch;
|
|
808
|
-
console.error("TransportWorker.processBatch failed", error);
|
|
809
|
-
await new Promise((resolve) => setTimeout(resolve, this.config.intervalMs));
|
|
810
|
-
} finally {
|
|
811
|
-
this.inFlightPromise = null;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
async flush(timeoutMs) {
|
|
816
|
-
const start = Date.now();
|
|
817
|
-
while ((this.queue.size() > 0 || this.pendingBatch || this.inFlightPromise) && Date.now() - start < timeoutMs) {
|
|
818
|
-
await new Promise((resolve) => setTimeout(resolve, this.config.intervalMs));
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
async close(timeoutMs = this.config.intervalMs * 50) {
|
|
822
|
-
this.closed = true;
|
|
823
|
-
const deadline = Date.now() + timeoutMs;
|
|
824
|
-
const awaitWithTimeout = async (promise, label, timeoutMs2, warnOnTimeout = true) => {
|
|
825
|
-
if (timeoutMs2 <= 0) {
|
|
826
|
-
if (warnOnTimeout) {
|
|
827
|
-
console.warn(`TransportWorker.close timed out waiting for ${label}`);
|
|
828
|
-
}
|
|
829
|
-
return false;
|
|
830
|
-
}
|
|
831
|
-
const resolvedPromise = Promise.resolve(promise);
|
|
832
|
-
let timeoutId = null;
|
|
833
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
834
|
-
timeoutId = setTimeout(() => resolve(false), timeoutMs2);
|
|
835
|
-
});
|
|
836
|
-
const completed = await Promise.race([resolvedPromise.then(() => true), timeoutPromise]);
|
|
837
|
-
if (timeoutId) {
|
|
838
|
-
clearTimeout(timeoutId);
|
|
839
|
-
}
|
|
840
|
-
if (!completed && warnOnTimeout) {
|
|
841
|
-
console.warn(`TransportWorker.close timed out waiting for ${label}`);
|
|
842
|
-
}
|
|
843
|
-
return completed;
|
|
844
|
-
};
|
|
845
|
-
const remainingTime = () => Math.max(0, deadline - Date.now());
|
|
846
|
-
const loopCompleted = await awaitWithTimeout(this.loopPromise, "loop to finish", remainingTime(), false);
|
|
847
|
-
if (!loopCompleted) {
|
|
848
|
-
console.warn("TransportWorker.close timed out waiting for loop to finish; closing transport now may cause potential data loss");
|
|
849
|
-
}
|
|
850
|
-
const safeTransportClose = async () => {
|
|
851
|
-
try {
|
|
852
|
-
await this.transport.close();
|
|
853
|
-
} catch (error) {
|
|
854
|
-
console.error("TransportWorker.close failed", error);
|
|
855
|
-
}
|
|
856
|
-
};
|
|
857
|
-
await awaitWithTimeout(safeTransportClose(), "transport to close", remainingTime());
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// packages/core/src/create-core.ts
|
|
862
|
-
function createCore(config) {
|
|
863
|
-
let transport;
|
|
864
|
-
if (config.transportType === "stdio") {
|
|
865
|
-
transport = new StdioTransport;
|
|
866
|
-
} else {
|
|
867
|
-
if (!config.httpConfig) {
|
|
868
|
-
throw new Error("HTTP transport requires httpConfig to be provided in configuration");
|
|
869
|
-
}
|
|
870
|
-
const httpConfig = HttpTransportConfigSchema.parse(config.httpConfig);
|
|
871
|
-
transport = new HttpTransport(httpConfig);
|
|
872
|
-
}
|
|
873
|
-
let partition;
|
|
874
|
-
if (config.httpConfig?.agentId) {
|
|
875
|
-
try {
|
|
876
|
-
partition = import_pfid2.extractPartition(config.httpConfig.agentId);
|
|
877
|
-
} catch {
|
|
878
|
-
partition = undefined;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
const queue = new InMemoryQueue;
|
|
882
|
-
const worker = new TransportWorker(queue, transport, { batchSize: 25, intervalMs: 50 });
|
|
883
|
-
const tracer = new Tracer(queue, partition);
|
|
884
|
-
const schemaName = config.httpConfig?.schemaName ?? "prefactor:agent";
|
|
885
|
-
const schemaIdentifier = config.httpConfig?.schemaIdentifier ?? "1.0.0";
|
|
886
|
-
const allowUnregisteredSchema = config.transportType === "http" && Boolean(config.httpConfig?.skipSchema || config.httpConfig?.agentSchema || config.httpConfig?.agentSchemaIdentifier);
|
|
887
|
-
const agentManager = new AgentInstanceManager(queue, {
|
|
888
|
-
schemaName,
|
|
889
|
-
schemaIdentifier,
|
|
890
|
-
allowUnregisteredSchema
|
|
891
|
-
});
|
|
892
|
-
const shutdown = async () => {
|
|
893
|
-
await worker.close();
|
|
894
|
-
};
|
|
895
|
-
return { tracer, agentManager, worker, shutdown };
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
//# debugId=CE5CB6A1D11DF5BA64756E2164756E21
|
|
1018
|
+
//# debugId=97A3CED64412936764756E2164756E21
|