@stackbone/sdk 0.1.0-alpha.2 → 0.1.0-alpha.4
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/CHANGELOG.md +307 -1
- package/README.md +127 -167
- package/db/index.d.cts +1 -0
- package/db/index.d.ts +1 -0
- package/db/testing/index.cjs +12 -43
- package/db/testing/index.cjs.map +1 -1
- package/db/testing/index.d.cts +12 -21
- package/db/testing/index.d.ts +12 -21
- package/db/testing/index.js +12 -43
- package/db/testing/index.js.map +1 -1
- package/index.cjs +7876 -19124
- package/index.cjs.map +1 -1
- package/index.d.cts +1434 -768
- package/index.d.ts +1434 -768
- package/index.js +7844 -19105
- package/index.js.map +1 -1
- package/observability/index.cjs +610 -0
- package/observability/index.cjs.map +1 -0
- package/observability/index.d.cts +175 -0
- package/observability/index.d.ts +175 -0
- package/observability/index.js +601 -0
- package/observability/index.js.map +1 -0
- package/package.json +14 -12
- package/rag/schema.cjs +42 -82
- package/rag/schema.cjs.map +1 -1
- package/rag/schema.d.cts +1 -446
- package/rag/schema.d.ts +1 -446
- package/rag/schema.js +42 -61
- package/rag/schema.js.map +1 -1
- package/stackbone-sdk-0.1.0-alpha.4.tgz +0 -0
- package/rag/migrations/index.cjs +0 -71
- package/rag/migrations/index.cjs.map +0 -1
- package/rag/migrations/index.d.cts +0 -29
- package/rag/migrations/index.d.ts +0 -29
- package/rag/migrations/index.js +0 -66
- package/rag/migrations/index.js.map +0 -1
- package/stackbone-sdk-0.1.0-alpha.2.tgz +0 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promises = require('fs/promises');
|
|
4
|
+
var os = require('os');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var async_hooks = require('async_hooks');
|
|
7
|
+
var crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
11
|
+
|
|
12
|
+
// package.json
|
|
13
|
+
var version = "0.1.0-alpha.2";
|
|
14
|
+
var storage = new async_hooks.AsyncLocalStorage();
|
|
15
|
+
function getInvocationContext() {
|
|
16
|
+
return storage.getStore();
|
|
17
|
+
}
|
|
18
|
+
__name(getInvocationContext, "getInvocationContext");
|
|
19
|
+
|
|
20
|
+
// src/observability/logger.ts
|
|
21
|
+
var LOG_LEVELS = {
|
|
22
|
+
trace: 10,
|
|
23
|
+
debug: 20,
|
|
24
|
+
info: 30,
|
|
25
|
+
warn: 40,
|
|
26
|
+
error: 50,
|
|
27
|
+
fatal: 60
|
|
28
|
+
};
|
|
29
|
+
var DEFAULT_BATCH_SIZE = 50;
|
|
30
|
+
var DEFAULT_INTERVAL_MS = 1e3;
|
|
31
|
+
var DEFAULT_HTTP_TIMEOUT_MS = 5e3;
|
|
32
|
+
function createPlatformLogger(options) {
|
|
33
|
+
const otelEndpoint = options.otelEndpoint ?? void 0;
|
|
34
|
+
const mode = options.mode ?? (otelEndpoint ? "cloud" : "local");
|
|
35
|
+
const baseBindings = {
|
|
36
|
+
run_id: options.runId
|
|
37
|
+
};
|
|
38
|
+
if (options.installationId !== void 0) baseBindings["installation_id"] = options.installationId;
|
|
39
|
+
if (options.agentId !== void 0) baseBindings["agent_id"] = options.agentId;
|
|
40
|
+
const destination = mode === "cloud" ? new OtlpHttpDestination({
|
|
41
|
+
endpoint: otelEndpoint ?? "",
|
|
42
|
+
resourceAttributes: options.resourceAttributes ?? {},
|
|
43
|
+
httpTimeoutMs: options.httpTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
|
|
44
|
+
flushBatchSize: options.flushBatchSize ?? DEFAULT_BATCH_SIZE,
|
|
45
|
+
flushIntervalMs: options.flushIntervalMs ?? DEFAULT_INTERVAL_MS,
|
|
46
|
+
...options.fetchImpl !== void 0 ? {
|
|
47
|
+
fetchImpl: options.fetchImpl
|
|
48
|
+
} : {}
|
|
49
|
+
}) : new JsonlFileDestination({
|
|
50
|
+
runId: options.runId,
|
|
51
|
+
runsDir: options.runsDir ?? defaultRunsDir(),
|
|
52
|
+
...options.appendFileImpl !== void 0 ? {
|
|
53
|
+
appendFileImpl: options.appendFileImpl
|
|
54
|
+
} : {},
|
|
55
|
+
...options.mkdirImpl !== void 0 ? {
|
|
56
|
+
mkdirImpl: options.mkdirImpl
|
|
57
|
+
} : {}
|
|
58
|
+
});
|
|
59
|
+
const now = options.now ?? Date.now;
|
|
60
|
+
return buildLogger({
|
|
61
|
+
destination,
|
|
62
|
+
baseBindings,
|
|
63
|
+
mode,
|
|
64
|
+
now
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
__name(createPlatformLogger, "createPlatformLogger");
|
|
68
|
+
function buildLogger(opts) {
|
|
69
|
+
const writeAt = /* @__PURE__ */ __name((level, msgOrObj, msg) => {
|
|
70
|
+
const time = opts.now();
|
|
71
|
+
const levelNumber = LOG_LEVELS[level];
|
|
72
|
+
const record = {
|
|
73
|
+
level: levelNumber,
|
|
74
|
+
time,
|
|
75
|
+
msg: "",
|
|
76
|
+
run_id: String(opts.baseBindings["run_id"] ?? ""),
|
|
77
|
+
...opts.baseBindings
|
|
78
|
+
};
|
|
79
|
+
if (typeof msgOrObj === "string") {
|
|
80
|
+
record.msg = msgOrObj;
|
|
81
|
+
} else if (msgOrObj && typeof msgOrObj === "object") {
|
|
82
|
+
Object.assign(record, msgOrObj);
|
|
83
|
+
if (typeof msg === "string") record.msg = msg;
|
|
84
|
+
else if (typeof record.msg !== "string") record.msg = "";
|
|
85
|
+
}
|
|
86
|
+
const ctx = getInvocationContext();
|
|
87
|
+
if (ctx) {
|
|
88
|
+
if (record.trace_id === void 0) record.trace_id = ctx.invocationId;
|
|
89
|
+
if (!record.run_id) record.run_id = ctx.runId;
|
|
90
|
+
if (record["handler"] === void 0 && ctx.handler !== void 0) {
|
|
91
|
+
record["handler"] = ctx.handler;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
opts.destination.write(record);
|
|
95
|
+
}, "writeAt");
|
|
96
|
+
const child = /* @__PURE__ */ __name((bindings) => buildLogger({
|
|
97
|
+
...opts,
|
|
98
|
+
baseBindings: {
|
|
99
|
+
...opts.baseBindings,
|
|
100
|
+
...bindings
|
|
101
|
+
}
|
|
102
|
+
}), "child");
|
|
103
|
+
return {
|
|
104
|
+
mode: opts.mode,
|
|
105
|
+
trace: /* @__PURE__ */ __name((msgOrObj, msg) => writeAt("trace", msgOrObj, msg), "trace"),
|
|
106
|
+
debug: /* @__PURE__ */ __name((msgOrObj, msg) => writeAt("debug", msgOrObj, msg), "debug"),
|
|
107
|
+
info: /* @__PURE__ */ __name((msgOrObj, msg) => writeAt("info", msgOrObj, msg), "info"),
|
|
108
|
+
warn: /* @__PURE__ */ __name((msgOrObj, msg) => writeAt("warn", msgOrObj, msg), "warn"),
|
|
109
|
+
error: /* @__PURE__ */ __name((msgOrObj, msg) => writeAt("error", msgOrObj, msg), "error"),
|
|
110
|
+
fatal: /* @__PURE__ */ __name((msgOrObj, msg) => writeAt("fatal", msgOrObj, msg), "fatal"),
|
|
111
|
+
child,
|
|
112
|
+
flush: /* @__PURE__ */ __name(() => opts.destination.flush(), "flush"),
|
|
113
|
+
close: /* @__PURE__ */ __name(() => opts.destination.close(), "close")
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
__name(buildLogger, "buildLogger");
|
|
117
|
+
var JsonlFileDestination = class JsonlFileDestination2 {
|
|
118
|
+
static {
|
|
119
|
+
__name(this, "JsonlFileDestination");
|
|
120
|
+
}
|
|
121
|
+
path;
|
|
122
|
+
appendFile;
|
|
123
|
+
mkdir;
|
|
124
|
+
inFlight = Promise.resolve();
|
|
125
|
+
dirReady = false;
|
|
126
|
+
constructor(opts) {
|
|
127
|
+
this.path = path.resolve(opts.runsDir, `${opts.runId}.jsonl`);
|
|
128
|
+
this.appendFile = opts.appendFileImpl ?? ((p, d) => promises.appendFile(p, d, "utf8"));
|
|
129
|
+
this.mkdir = opts.mkdirImpl ?? (async (p) => {
|
|
130
|
+
await promises.mkdir(p, {
|
|
131
|
+
recursive: true
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
write(record) {
|
|
136
|
+
const line = `${JSON.stringify(record)}
|
|
137
|
+
`;
|
|
138
|
+
this.inFlight = this.inFlight.then(async () => {
|
|
139
|
+
if (!this.dirReady) {
|
|
140
|
+
await this.mkdir(path.dirname(this.path));
|
|
141
|
+
this.dirReady = true;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await this.appendFile(this.path, line);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
process.stderr.write(`[stackbone/sdk] PlatformLogger JSONL write failed: ${error instanceof Error ? error.message : String(error)}
|
|
147
|
+
`);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async flush() {
|
|
152
|
+
await this.inFlight;
|
|
153
|
+
}
|
|
154
|
+
async close() {
|
|
155
|
+
await this.flush();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var OtlpHttpDestination = class OtlpHttpDestination2 {
|
|
159
|
+
static {
|
|
160
|
+
__name(this, "OtlpHttpDestination");
|
|
161
|
+
}
|
|
162
|
+
url;
|
|
163
|
+
resourceAttributes;
|
|
164
|
+
httpTimeoutMs;
|
|
165
|
+
flushBatchSize;
|
|
166
|
+
flushIntervalMs;
|
|
167
|
+
fetchImpl;
|
|
168
|
+
buffer = [];
|
|
169
|
+
timer = null;
|
|
170
|
+
inFlight = Promise.resolve();
|
|
171
|
+
closed = false;
|
|
172
|
+
constructor(opts) {
|
|
173
|
+
this.url = `${opts.endpoint.replace(/\/+$/, "")}/v1/logs`;
|
|
174
|
+
this.resourceAttributes = opts.resourceAttributes;
|
|
175
|
+
this.httpTimeoutMs = opts.httpTimeoutMs;
|
|
176
|
+
this.flushBatchSize = opts.flushBatchSize;
|
|
177
|
+
this.flushIntervalMs = opts.flushIntervalMs;
|
|
178
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
179
|
+
}
|
|
180
|
+
write(record) {
|
|
181
|
+
if (this.closed) return;
|
|
182
|
+
this.buffer.push(record);
|
|
183
|
+
if (this.buffer.length >= this.flushBatchSize) {
|
|
184
|
+
void this.flush();
|
|
185
|
+
} else {
|
|
186
|
+
this.armTimer();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async flush() {
|
|
190
|
+
await this.inFlight;
|
|
191
|
+
if (this.buffer.length === 0) return;
|
|
192
|
+
const drained = this.buffer.splice(0, this.buffer.length);
|
|
193
|
+
this.disarmTimer();
|
|
194
|
+
this.inFlight = this.post(drained).catch((error) => {
|
|
195
|
+
process.stderr.write(`[stackbone/sdk] PlatformLogger OTLP POST failed: ${error instanceof Error ? error.message : String(error)}
|
|
196
|
+
`);
|
|
197
|
+
});
|
|
198
|
+
await this.inFlight;
|
|
199
|
+
}
|
|
200
|
+
async close() {
|
|
201
|
+
this.closed = true;
|
|
202
|
+
await this.flush();
|
|
203
|
+
}
|
|
204
|
+
armTimer() {
|
|
205
|
+
if (this.timer) return;
|
|
206
|
+
this.timer = setTimeout(() => {
|
|
207
|
+
this.timer = null;
|
|
208
|
+
void this.flush();
|
|
209
|
+
}, this.flushIntervalMs);
|
|
210
|
+
this.timer.unref?.();
|
|
211
|
+
}
|
|
212
|
+
disarmTimer() {
|
|
213
|
+
if (this.timer) {
|
|
214
|
+
clearTimeout(this.timer);
|
|
215
|
+
this.timer = null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async post(records) {
|
|
219
|
+
const body = JSON.stringify(buildOtlpLogsBody(records, this.resourceAttributes));
|
|
220
|
+
const controller = new AbortController();
|
|
221
|
+
const timeout = setTimeout(() => controller.abort(), this.httpTimeoutMs);
|
|
222
|
+
try {
|
|
223
|
+
const resp = await this.fetchImpl(this.url, {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: {
|
|
226
|
+
"content-type": "application/json"
|
|
227
|
+
},
|
|
228
|
+
body,
|
|
229
|
+
signal: controller.signal
|
|
230
|
+
});
|
|
231
|
+
if (!resp.ok) {
|
|
232
|
+
const text = await resp.text().catch(() => "");
|
|
233
|
+
throw new Error(`HTTP ${resp.status}${text ? `: ${text.slice(0, 200)}` : ""}`);
|
|
234
|
+
}
|
|
235
|
+
} finally {
|
|
236
|
+
clearTimeout(timeout);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var SDK_SCOPE = {
|
|
241
|
+
name: "@stackbone/sdk",
|
|
242
|
+
version
|
|
243
|
+
};
|
|
244
|
+
function buildOtlpLogsBody(records, resourceAttributes) {
|
|
245
|
+
return {
|
|
246
|
+
resourceLogs: [
|
|
247
|
+
{
|
|
248
|
+
resource: {
|
|
249
|
+
attributes: toOtlpAttributes(resourceAttributes)
|
|
250
|
+
},
|
|
251
|
+
scopeLogs: [
|
|
252
|
+
{
|
|
253
|
+
scope: SDK_SCOPE,
|
|
254
|
+
logRecords: records.map(toOtlpLogRecord)
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
__name(buildOtlpLogsBody, "buildOtlpLogsBody");
|
|
262
|
+
function toOtlpLogRecord(record) {
|
|
263
|
+
const { level, time, msg, ...rest } = record;
|
|
264
|
+
return {
|
|
265
|
+
timeUnixNano: `${time * 1e6}`,
|
|
266
|
+
severityNumber: level,
|
|
267
|
+
severityText: severityText(level),
|
|
268
|
+
body: {
|
|
269
|
+
stringValue: msg
|
|
270
|
+
},
|
|
271
|
+
attributes: toOtlpAttributesFromUnknown(rest)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
__name(toOtlpLogRecord, "toOtlpLogRecord");
|
|
275
|
+
var SEVERITY_TEXT_BY_LEVEL = Object.fromEntries(Object.entries(LOG_LEVELS).map(([name, value]) => [
|
|
276
|
+
value,
|
|
277
|
+
name.toUpperCase()
|
|
278
|
+
]));
|
|
279
|
+
function severityText(level) {
|
|
280
|
+
return SEVERITY_TEXT_BY_LEVEL[level] ?? "INFO";
|
|
281
|
+
}
|
|
282
|
+
__name(severityText, "severityText");
|
|
283
|
+
function toOtlpAttributes(attrs) {
|
|
284
|
+
return Object.entries(attrs).map(([key, value]) => ({
|
|
285
|
+
key,
|
|
286
|
+
value: {
|
|
287
|
+
stringValue: value
|
|
288
|
+
}
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
__name(toOtlpAttributes, "toOtlpAttributes");
|
|
292
|
+
function toOtlpAttributesFromUnknown(attrs) {
|
|
293
|
+
const out = [];
|
|
294
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
295
|
+
if (value === null || value === void 0) continue;
|
|
296
|
+
if (typeof value === "string") {
|
|
297
|
+
out.push({
|
|
298
|
+
key,
|
|
299
|
+
value: {
|
|
300
|
+
stringValue: value
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
} else if (typeof value === "number") {
|
|
304
|
+
if (Number.isInteger(value)) out.push({
|
|
305
|
+
key,
|
|
306
|
+
value: {
|
|
307
|
+
intValue: String(value)
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
else out.push({
|
|
311
|
+
key,
|
|
312
|
+
value: {
|
|
313
|
+
doubleValue: value
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
} else if (typeof value === "boolean") {
|
|
317
|
+
out.push({
|
|
318
|
+
key,
|
|
319
|
+
value: {
|
|
320
|
+
boolValue: value
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
out.push({
|
|
325
|
+
key,
|
|
326
|
+
value: {
|
|
327
|
+
stringValue: JSON.stringify(value)
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
__name(toOtlpAttributesFromUnknown, "toOtlpAttributesFromUnknown");
|
|
335
|
+
function defaultRunsDir() {
|
|
336
|
+
return path.resolve(os.homedir(), ".stackbone", "dev", "runs");
|
|
337
|
+
}
|
|
338
|
+
__name(defaultRunsDir, "defaultRunsDir");
|
|
339
|
+
var RUN_STEP_TYPES = [
|
|
340
|
+
"agent",
|
|
341
|
+
"llm_call",
|
|
342
|
+
"db_query",
|
|
343
|
+
"http_fetch",
|
|
344
|
+
"queue_publish",
|
|
345
|
+
"hitl_pause",
|
|
346
|
+
"tool_call",
|
|
347
|
+
"rag_query",
|
|
348
|
+
"storage_op"
|
|
349
|
+
];
|
|
350
|
+
var STEP_TYPE_ATTRIBUTE = "stackbone.step.type";
|
|
351
|
+
var RUN_ID_ATTRIBUTE = "stackbone.run.id";
|
|
352
|
+
var NAME_PREFIX_MAP = [
|
|
353
|
+
[
|
|
354
|
+
/^stackbone\.ai\./,
|
|
355
|
+
"llm_call"
|
|
356
|
+
],
|
|
357
|
+
[
|
|
358
|
+
/^stackbone\.db\./,
|
|
359
|
+
"db_query"
|
|
360
|
+
],
|
|
361
|
+
[
|
|
362
|
+
/^stackbone\.http\./,
|
|
363
|
+
"http_fetch"
|
|
364
|
+
],
|
|
365
|
+
[
|
|
366
|
+
/^stackbone\.queues\./,
|
|
367
|
+
"queue_publish"
|
|
368
|
+
],
|
|
369
|
+
[
|
|
370
|
+
/^stackbone\.approval\./,
|
|
371
|
+
"hitl_pause"
|
|
372
|
+
],
|
|
373
|
+
[
|
|
374
|
+
/^stackbone\.tool\./,
|
|
375
|
+
"tool_call"
|
|
376
|
+
],
|
|
377
|
+
[
|
|
378
|
+
/^stackbone\.rag\./,
|
|
379
|
+
"rag_query"
|
|
380
|
+
],
|
|
381
|
+
[
|
|
382
|
+
/^stackbone\.storage\./,
|
|
383
|
+
"storage_op"
|
|
384
|
+
]
|
|
385
|
+
];
|
|
386
|
+
var DEFAULT_BATCH_SIZE2 = 50;
|
|
387
|
+
var DEFAULT_INTERVAL_MS2 = 500;
|
|
388
|
+
var RunStepsSpanProcessor = class {
|
|
389
|
+
static {
|
|
390
|
+
__name(this, "RunStepsSpanProcessor");
|
|
391
|
+
}
|
|
392
|
+
buffer = [];
|
|
393
|
+
stepIdBySpanId = /* @__PURE__ */ new Map();
|
|
394
|
+
batchSize;
|
|
395
|
+
intervalMs;
|
|
396
|
+
resolveStepType;
|
|
397
|
+
newId;
|
|
398
|
+
connectionString;
|
|
399
|
+
ownsSql;
|
|
400
|
+
sql;
|
|
401
|
+
timer = null;
|
|
402
|
+
inFlight = null;
|
|
403
|
+
constructor(options = {}) {
|
|
404
|
+
this.batchSize = options.flushBatchSize ?? DEFAULT_BATCH_SIZE2;
|
|
405
|
+
this.intervalMs = options.flushIntervalMs ?? DEFAULT_INTERVAL_MS2;
|
|
406
|
+
this.resolveStepType = options.resolveStepType ?? defaultStepTypeResolver;
|
|
407
|
+
this.newId = options.newId ?? crypto.randomUUID;
|
|
408
|
+
this.connectionString = options.connectionString;
|
|
409
|
+
if (options.sql) {
|
|
410
|
+
this.sql = options.sql;
|
|
411
|
+
this.ownsSql = false;
|
|
412
|
+
} else {
|
|
413
|
+
this.sql = null;
|
|
414
|
+
this.ownsSql = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// OTel calls onStart for the parent before any child, and onEnd for
|
|
418
|
+
// children before the parent. Reserving the UUID up-front lets the child's
|
|
419
|
+
// onEnd resolve `parent_step_id` synchronously — the alternative (looking
|
|
420
|
+
// up the parent at write time) would race the buffer flush.
|
|
421
|
+
onStart(span) {
|
|
422
|
+
const spanId = span.spanContext().spanId;
|
|
423
|
+
if (!this.stepIdBySpanId.has(spanId)) {
|
|
424
|
+
this.stepIdBySpanId.set(spanId, this.newId());
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
onEnd(span) {
|
|
428
|
+
const row = this.buildRow(span);
|
|
429
|
+
if (!row) return;
|
|
430
|
+
this.buffer.push(row);
|
|
431
|
+
if (this.buffer.length >= this.batchSize) {
|
|
432
|
+
void this.flush();
|
|
433
|
+
} else {
|
|
434
|
+
this.armTimer();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async forceFlush() {
|
|
438
|
+
await this.flush();
|
|
439
|
+
}
|
|
440
|
+
async shutdown() {
|
|
441
|
+
this.disarmTimer();
|
|
442
|
+
await this.flush();
|
|
443
|
+
if (this.ownsSql && this.sql?.end) {
|
|
444
|
+
await this.sql.end().catch(() => void 0);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
buildRow(span) {
|
|
448
|
+
const spanId = span.spanContext().spanId;
|
|
449
|
+
const stepId = this.stepIdBySpanId.get(spanId) ?? this.newId();
|
|
450
|
+
this.stepIdBySpanId.delete(spanId);
|
|
451
|
+
const runId = readString(span.attributes[RUN_ID_ATTRIBUTE]);
|
|
452
|
+
if (!runId) return null;
|
|
453
|
+
const parentSpanId = span.parentSpanContext?.spanId ?? span.parentSpanId ?? null;
|
|
454
|
+
const parentStepId = parentSpanId ? this.stepIdBySpanId.get(parentSpanId) ?? null : null;
|
|
455
|
+
const startedAt = formatHrTime(span.startTime);
|
|
456
|
+
const finishedAt = formatHrTime(span.endTime ?? span.startTime);
|
|
457
|
+
const durationMs = hrTimeToMs(span.duration ?? diffHrTime(span.endTime, span.startTime));
|
|
458
|
+
const isError = span.status?.code === 2;
|
|
459
|
+
return {
|
|
460
|
+
id: stepId,
|
|
461
|
+
run_id: runId,
|
|
462
|
+
parent_step_id: parentStepId,
|
|
463
|
+
type: this.resolveStepType(span),
|
|
464
|
+
name: span.name,
|
|
465
|
+
status: isError ? "error" : "ok",
|
|
466
|
+
payload: serialisablePayload(span.attributes),
|
|
467
|
+
error: isError ? {
|
|
468
|
+
message: span.status?.message ?? "span ended with ERROR status"
|
|
469
|
+
} : null,
|
|
470
|
+
started_at: startedAt,
|
|
471
|
+
finished_at: finishedAt,
|
|
472
|
+
duration_ms: durationMs
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
armTimer() {
|
|
476
|
+
if (this.timer) return;
|
|
477
|
+
this.timer = setTimeout(() => {
|
|
478
|
+
this.timer = null;
|
|
479
|
+
void this.flush();
|
|
480
|
+
}, this.intervalMs);
|
|
481
|
+
this.timer.unref?.();
|
|
482
|
+
}
|
|
483
|
+
disarmTimer() {
|
|
484
|
+
if (this.timer) {
|
|
485
|
+
clearTimeout(this.timer);
|
|
486
|
+
this.timer = null;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async flush() {
|
|
490
|
+
if (this.inFlight) {
|
|
491
|
+
await this.inFlight;
|
|
492
|
+
}
|
|
493
|
+
if (this.buffer.length === 0) return;
|
|
494
|
+
const drained = this.buffer.splice(0, this.buffer.length);
|
|
495
|
+
this.disarmTimer();
|
|
496
|
+
this.inFlight = this.writeBatch(drained).finally(() => {
|
|
497
|
+
this.inFlight = null;
|
|
498
|
+
});
|
|
499
|
+
await this.inFlight;
|
|
500
|
+
}
|
|
501
|
+
async ensureSql() {
|
|
502
|
+
if (this.sql) return this.sql;
|
|
503
|
+
if (!this.connectionString) return null;
|
|
504
|
+
const mod = await import('postgres');
|
|
505
|
+
this.sql = mod.default(this.connectionString);
|
|
506
|
+
return this.sql;
|
|
507
|
+
}
|
|
508
|
+
async writeBatch(rows) {
|
|
509
|
+
try {
|
|
510
|
+
const sql = await this.ensureSql();
|
|
511
|
+
if (!sql) {
|
|
512
|
+
process.stderr.write("[stackbone/sdk] RunStepsSpanProcessor dropped batch: no DATABASE_URL configured.\n");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const placeholders = [];
|
|
516
|
+
const params = [];
|
|
517
|
+
for (const row of rows) {
|
|
518
|
+
const base = params.length;
|
|
519
|
+
placeholders.push(`($${base + 1}::uuid, $${base + 2}::uuid, $${base + 3}::uuid, $${base + 4}, $${base + 5}, $${base + 6}, $${base + 7}::jsonb, $${base + 8}::jsonb, $${base + 9}::timestamptz, $${base + 10}::timestamptz, $${base + 11}::int)`);
|
|
520
|
+
params.push(row.id, row.run_id, row.parent_step_id, row.type, row.name, row.status, JSON.stringify(row.payload), row.error ? JSON.stringify(row.error) : null, row.started_at, row.finished_at, row.duration_ms);
|
|
521
|
+
}
|
|
522
|
+
const text = `INSERT INTO stackbone_platform.run_steps (
|
|
523
|
+
id, run_id, parent_step_id, type, name, status, payload, error,
|
|
524
|
+
started_at, finished_at, duration_ms
|
|
525
|
+
) VALUES ${placeholders.join(", ")}`;
|
|
526
|
+
await sql.unsafe(text, params);
|
|
527
|
+
} catch (error) {
|
|
528
|
+
process.stderr.write(`[stackbone/sdk] RunStepsSpanProcessor write failed: ${error instanceof Error ? error.message : String(error)}
|
|
529
|
+
`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
var defaultStepTypeResolver = /* @__PURE__ */ __name((span) => {
|
|
534
|
+
const explicit = readString(span.attributes[STEP_TYPE_ATTRIBUTE]);
|
|
535
|
+
if (explicit && RUN_STEP_TYPES.includes(explicit)) {
|
|
536
|
+
return explicit;
|
|
537
|
+
}
|
|
538
|
+
for (const [pattern, type] of NAME_PREFIX_MAP) {
|
|
539
|
+
if (pattern.test(span.name)) return type;
|
|
540
|
+
}
|
|
541
|
+
return "agent";
|
|
542
|
+
}, "defaultStepTypeResolver");
|
|
543
|
+
var readString = /* @__PURE__ */ __name((value) => typeof value === "string" && value.length > 0 ? value : void 0, "readString");
|
|
544
|
+
var formatHrTime = /* @__PURE__ */ __name((hr) => new Date(hr ? hrTimeToMs(hr) : Date.now()).toISOString(), "formatHrTime");
|
|
545
|
+
var hrTimeToMs = /* @__PURE__ */ __name((hr) => {
|
|
546
|
+
if (!hr) return 0;
|
|
547
|
+
const [seconds, nanos] = hr;
|
|
548
|
+
return seconds * 1e3 + Math.floor(nanos / 1e6);
|
|
549
|
+
}, "hrTimeToMs");
|
|
550
|
+
var diffHrTime = /* @__PURE__ */ __name((end, start) => {
|
|
551
|
+
if (!end || !start) return void 0;
|
|
552
|
+
const seconds = end[0] - start[0];
|
|
553
|
+
const nanos = end[1] - start[1];
|
|
554
|
+
return [
|
|
555
|
+
seconds,
|
|
556
|
+
nanos
|
|
557
|
+
];
|
|
558
|
+
}, "diffHrTime");
|
|
559
|
+
var serialisablePayload = /* @__PURE__ */ __name((attributes) => {
|
|
560
|
+
const out = {};
|
|
561
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
562
|
+
if (key === RUN_ID_ATTRIBUTE) continue;
|
|
563
|
+
if (key === STEP_TYPE_ATTRIBUTE) continue;
|
|
564
|
+
out[key] = value;
|
|
565
|
+
}
|
|
566
|
+
return out;
|
|
567
|
+
}, "serialisablePayload");
|
|
568
|
+
|
|
569
|
+
// src/observability/aggregate-run-cost.ts
|
|
570
|
+
var SQL = `WITH llm_costs AS (
|
|
571
|
+
SELECT COALESCE(SUM((payload->>'cost_usd')::numeric), 0) AS total
|
|
572
|
+
FROM stackbone_platform.run_steps
|
|
573
|
+
WHERE run_id = $1::uuid
|
|
574
|
+
AND type = 'llm_call'
|
|
575
|
+
)
|
|
576
|
+
UPDATE stackbone_platform.runs
|
|
577
|
+
SET cost_estimated_usd = (SELECT total FROM llm_costs)
|
|
578
|
+
WHERE id = $1::uuid
|
|
579
|
+
RETURNING cost_estimated_usd`;
|
|
580
|
+
async function aggregateRunCost(runId, options) {
|
|
581
|
+
const result = await options.sql.unsafe(SQL, [
|
|
582
|
+
runId
|
|
583
|
+
]);
|
|
584
|
+
const row = result[0];
|
|
585
|
+
if (!row) {
|
|
586
|
+
return {
|
|
587
|
+
costEstimatedUsd: 0
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
const raw = row.cost_estimated_usd;
|
|
591
|
+
if (raw === null || raw === void 0) return {
|
|
592
|
+
costEstimatedUsd: 0
|
|
593
|
+
};
|
|
594
|
+
const num = typeof raw === "string" ? Number(raw) : raw;
|
|
595
|
+
return {
|
|
596
|
+
costEstimatedUsd: Number.isFinite(num) ? num : 0
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
__name(aggregateRunCost, "aggregateRunCost");
|
|
600
|
+
|
|
601
|
+
exports.LOG_LEVELS = LOG_LEVELS;
|
|
602
|
+
exports.RUN_ID_ATTRIBUTE = RUN_ID_ATTRIBUTE;
|
|
603
|
+
exports.RUN_STEP_TYPES = RUN_STEP_TYPES;
|
|
604
|
+
exports.RunStepsSpanProcessor = RunStepsSpanProcessor;
|
|
605
|
+
exports.STEP_TYPE_ATTRIBUTE = STEP_TYPE_ATTRIBUTE;
|
|
606
|
+
exports.aggregateRunCost = aggregateRunCost;
|
|
607
|
+
exports.createPlatformLogger = createPlatformLogger;
|
|
608
|
+
exports.defaultRunsDir = defaultRunsDir;
|
|
609
|
+
//# sourceMappingURL=index.cjs.map
|
|
610
|
+
//# sourceMappingURL=index.cjs.map
|