neatlogs 1.0.8 → 1.0.9
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/index.cjs +548 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +548 -122
- package/dist/index.mjs.map +1 -1
- package/dist/opencode-plugin.cjs +530 -5727
- package/dist/opencode-plugin.cjs.map +1 -1
- package/dist/opencode-plugin.d.ts +17 -16
- package/dist/opencode-plugin.mjs +530 -5740
- package/dist/opencode-plugin.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -5825,7 +5825,7 @@ async function removeTag(name, tag) {
|
|
|
5825
5825
|
}
|
|
5826
5826
|
|
|
5827
5827
|
// src/version.ts
|
|
5828
|
-
var __version__ = "1.0.
|
|
5828
|
+
var __version__ = "1.0.9";
|
|
5829
5829
|
|
|
5830
5830
|
// src/init.ts
|
|
5831
5831
|
var logger13 = getLogger();
|
|
@@ -10263,189 +10263,615 @@ function recordError8(span2, err) {
|
|
|
10263
10263
|
span2.end();
|
|
10264
10264
|
}
|
|
10265
10265
|
|
|
10266
|
-
// src/opencode-
|
|
10267
|
-
import
|
|
10268
|
-
var
|
|
10269
|
-
var
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10266
|
+
// src/opencode-trace-shipper.ts
|
|
10267
|
+
import protobuf from "protobufjs";
|
|
10268
|
+
var PACKAGE_NAME = "neatlogs.opencode";
|
|
10269
|
+
var OTLP_PROTO_JSON = {
|
|
10270
|
+
nested: {
|
|
10271
|
+
opentelemetry: {
|
|
10272
|
+
nested: {
|
|
10273
|
+
proto: {
|
|
10274
|
+
nested: {
|
|
10275
|
+
common: {
|
|
10276
|
+
nested: {
|
|
10277
|
+
v1: {
|
|
10278
|
+
nested: {
|
|
10279
|
+
AnyValue: {
|
|
10280
|
+
oneofs: {
|
|
10281
|
+
value: {
|
|
10282
|
+
oneof: [
|
|
10283
|
+
"stringValue",
|
|
10284
|
+
"boolValue",
|
|
10285
|
+
"intValue",
|
|
10286
|
+
"doubleValue",
|
|
10287
|
+
"arrayValue",
|
|
10288
|
+
"kvlistValue",
|
|
10289
|
+
"bytesValue"
|
|
10290
|
+
]
|
|
10291
|
+
}
|
|
10292
|
+
},
|
|
10293
|
+
fields: {
|
|
10294
|
+
stringValue: { type: "string", id: 1 },
|
|
10295
|
+
boolValue: { type: "bool", id: 2 },
|
|
10296
|
+
intValue: { type: "int64", id: 3 },
|
|
10297
|
+
doubleValue: { type: "double", id: 4 },
|
|
10298
|
+
arrayValue: { type: "ArrayValue", id: 5 },
|
|
10299
|
+
kvlistValue: { type: "KeyValueList", id: 6 },
|
|
10300
|
+
bytesValue: { type: "bytes", id: 7 }
|
|
10301
|
+
}
|
|
10302
|
+
},
|
|
10303
|
+
ArrayValue: { fields: { values: { rule: "repeated", type: "AnyValue", id: 1 } } },
|
|
10304
|
+
KeyValueList: { fields: { values: { rule: "repeated", type: "KeyValue", id: 1 } } },
|
|
10305
|
+
KeyValue: {
|
|
10306
|
+
fields: { key: { type: "string", id: 1 }, value: { type: "AnyValue", id: 2 } }
|
|
10307
|
+
},
|
|
10308
|
+
InstrumentationScope: {
|
|
10309
|
+
fields: { name: { type: "string", id: 1 }, version: { type: "string", id: 2 } }
|
|
10310
|
+
}
|
|
10311
|
+
}
|
|
10312
|
+
}
|
|
10313
|
+
}
|
|
10314
|
+
},
|
|
10315
|
+
resource: {
|
|
10316
|
+
nested: {
|
|
10317
|
+
v1: {
|
|
10318
|
+
nested: {
|
|
10319
|
+
Resource: {
|
|
10320
|
+
fields: {
|
|
10321
|
+
attributes: {
|
|
10322
|
+
rule: "repeated",
|
|
10323
|
+
type: "opentelemetry.proto.common.v1.KeyValue",
|
|
10324
|
+
id: 1
|
|
10325
|
+
}
|
|
10326
|
+
}
|
|
10327
|
+
}
|
|
10328
|
+
}
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
},
|
|
10332
|
+
trace: {
|
|
10333
|
+
nested: {
|
|
10334
|
+
v1: {
|
|
10335
|
+
nested: {
|
|
10336
|
+
ResourceSpans: {
|
|
10337
|
+
fields: {
|
|
10338
|
+
resource: { type: "opentelemetry.proto.resource.v1.Resource", id: 1 },
|
|
10339
|
+
scopeSpans: { rule: "repeated", type: "ScopeSpans", id: 2 }
|
|
10340
|
+
}
|
|
10341
|
+
},
|
|
10342
|
+
ScopeSpans: {
|
|
10343
|
+
fields: {
|
|
10344
|
+
scope: { type: "opentelemetry.proto.common.v1.InstrumentationScope", id: 1 },
|
|
10345
|
+
spans: { rule: "repeated", type: "Span", id: 2 }
|
|
10346
|
+
}
|
|
10347
|
+
},
|
|
10348
|
+
Span: {
|
|
10349
|
+
fields: {
|
|
10350
|
+
traceId: { type: "bytes", id: 1 },
|
|
10351
|
+
spanId: { type: "bytes", id: 2 },
|
|
10352
|
+
traceState: { type: "string", id: 3 },
|
|
10353
|
+
parentSpanId: { type: "bytes", id: 4 },
|
|
10354
|
+
name: { type: "string", id: 5 },
|
|
10355
|
+
kind: { type: "SpanKind", id: 6 },
|
|
10356
|
+
startTimeUnixNano: { type: "fixed64", id: 7 },
|
|
10357
|
+
endTimeUnixNano: { type: "fixed64", id: 8 },
|
|
10358
|
+
attributes: {
|
|
10359
|
+
rule: "repeated",
|
|
10360
|
+
type: "opentelemetry.proto.common.v1.KeyValue",
|
|
10361
|
+
id: 9
|
|
10362
|
+
},
|
|
10363
|
+
droppedAttributesCount: { type: "uint32", id: 10 },
|
|
10364
|
+
status: { type: "Status", id: 15 }
|
|
10365
|
+
}
|
|
10366
|
+
},
|
|
10367
|
+
Status: {
|
|
10368
|
+
fields: { message: { type: "string", id: 2 }, code: { type: "StatusCode", id: 3 } }
|
|
10369
|
+
},
|
|
10370
|
+
StatusCode: {
|
|
10371
|
+
values: { STATUS_CODE_UNSET: 0, STATUS_CODE_OK: 1, STATUS_CODE_ERROR: 2 }
|
|
10372
|
+
},
|
|
10373
|
+
SpanKind: {
|
|
10374
|
+
values: {
|
|
10375
|
+
SPAN_KIND_UNSPECIFIED: 0,
|
|
10376
|
+
SPAN_KIND_INTERNAL: 1,
|
|
10377
|
+
SPAN_KIND_SERVER: 2,
|
|
10378
|
+
SPAN_KIND_CLIENT: 3,
|
|
10379
|
+
SPAN_KIND_PRODUCER: 4,
|
|
10380
|
+
SPAN_KIND_CONSUMER: 5
|
|
10381
|
+
}
|
|
10382
|
+
}
|
|
10383
|
+
}
|
|
10384
|
+
}
|
|
10385
|
+
}
|
|
10386
|
+
},
|
|
10387
|
+
collector: {
|
|
10388
|
+
nested: {
|
|
10389
|
+
trace: {
|
|
10390
|
+
nested: {
|
|
10391
|
+
v1: {
|
|
10392
|
+
nested: {
|
|
10393
|
+
ExportTraceServiceRequest: {
|
|
10394
|
+
fields: {
|
|
10395
|
+
resourceSpans: {
|
|
10396
|
+
rule: "repeated",
|
|
10397
|
+
type: "opentelemetry.proto.trace.v1.ResourceSpans",
|
|
10398
|
+
id: 1
|
|
10399
|
+
}
|
|
10400
|
+
}
|
|
10401
|
+
}
|
|
10402
|
+
}
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
10405
|
+
}
|
|
10406
|
+
}
|
|
10407
|
+
}
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
}
|
|
10412
|
+
}
|
|
10413
|
+
};
|
|
10414
|
+
var protoRoot = protobuf.Root.fromJSON(OTLP_PROTO_JSON);
|
|
10415
|
+
var ExportTraceServiceRequest = protoRoot.lookupType(
|
|
10416
|
+
"opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest"
|
|
10417
|
+
);
|
|
10418
|
+
var SpanStatusCode16 = { UNSET: 0, OK: 1, ERROR: 2 };
|
|
10419
|
+
function randomBytes3(length) {
|
|
10420
|
+
const out = new Uint8Array(length);
|
|
10421
|
+
const crypto2 = globalThis.crypto;
|
|
10422
|
+
if (crypto2 && typeof crypto2.getRandomValues === "function") {
|
|
10423
|
+
crypto2.getRandomValues(out);
|
|
10424
|
+
return out;
|
|
10425
|
+
}
|
|
10426
|
+
for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
|
|
10427
|
+
return out;
|
|
10428
|
+
}
|
|
10429
|
+
function generateTraceId() {
|
|
10430
|
+
return randomBytes3(16);
|
|
10431
|
+
}
|
|
10432
|
+
function generateSpanId() {
|
|
10433
|
+
return randomBytes3(8);
|
|
10434
|
+
}
|
|
10435
|
+
function nowNanoString() {
|
|
10436
|
+
return (BigInt(Date.now()) * 1000000n).toString();
|
|
10437
|
+
}
|
|
10438
|
+
function msToNanoString(ms) {
|
|
10439
|
+
return (BigInt(Math.floor(ms)) * 1000000n).toString();
|
|
10440
|
+
}
|
|
10441
|
+
function attrStr(key, value) {
|
|
10442
|
+
if (value === void 0 || value === null) return void 0;
|
|
10443
|
+
return { key, value: { stringValue: String(value) } };
|
|
10444
|
+
}
|
|
10445
|
+
function attrInt(key, value) {
|
|
10446
|
+
if (value === void 0 || value === null || !Number.isFinite(value)) return void 0;
|
|
10447
|
+
return { key, value: { intValue: String(Math.trunc(value)) } };
|
|
10448
|
+
}
|
|
10449
|
+
function attrDouble(key, value) {
|
|
10450
|
+
if (value === void 0 || value === null || !Number.isFinite(value)) return void 0;
|
|
10451
|
+
return { key, value: { doubleValue: value } };
|
|
10452
|
+
}
|
|
10453
|
+
var TraceShipper = class {
|
|
10454
|
+
apiKey;
|
|
10455
|
+
endpoint;
|
|
10456
|
+
debug;
|
|
10457
|
+
maxRetries;
|
|
10458
|
+
workflowName;
|
|
10459
|
+
queue = [];
|
|
10460
|
+
prefix = "[neatlogs/opencode]";
|
|
10461
|
+
constructor(opts) {
|
|
10462
|
+
this.apiKey = opts.apiKey;
|
|
10463
|
+
this.endpoint = opts.endpoint.endsWith("/") ? opts.endpoint.slice(0, -1) : opts.endpoint;
|
|
10464
|
+
this.debug = !!opts.debug;
|
|
10465
|
+
this.maxRetries = opts.maxRetries ?? 3;
|
|
10466
|
+
this.workflowName = opts.workflowName || "";
|
|
10467
|
+
}
|
|
10468
|
+
enqueue(span2) {
|
|
10469
|
+
this.queue.push(span2);
|
|
10470
|
+
}
|
|
10471
|
+
get pending() {
|
|
10472
|
+
return this.queue.length;
|
|
10473
|
+
}
|
|
10474
|
+
/**
|
|
10475
|
+
* Ship all queued spans in a single awaited POST. Resolves only once the HTTP
|
|
10476
|
+
* response is received (or all retries are exhausted) — so a short-lived host
|
|
10477
|
+
* can safely exit immediately after awaiting this.
|
|
10478
|
+
*/
|
|
10479
|
+
async flush() {
|
|
10480
|
+
if (this.queue.length === 0) return;
|
|
10481
|
+
if (!this.apiKey) {
|
|
10482
|
+
this.queue = [];
|
|
10483
|
+
return;
|
|
10484
|
+
}
|
|
10485
|
+
const spans = this.queue.splice(0);
|
|
10486
|
+
const payload = this.buildProtobuf(spans);
|
|
10487
|
+
const url = `${this.endpoint}/v1/traces`;
|
|
10488
|
+
if (this.debug) console.log(`${this.prefix} Shipping ${spans.length} spans to ${url}`);
|
|
10489
|
+
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
10490
|
+
try {
|
|
10491
|
+
const resp = await fetch(url, {
|
|
10492
|
+
method: "POST",
|
|
10493
|
+
headers: {
|
|
10494
|
+
"Content-Type": "application/x-protobuf",
|
|
10495
|
+
"x-api-key": this.apiKey
|
|
10496
|
+
},
|
|
10497
|
+
body: payload
|
|
10498
|
+
});
|
|
10499
|
+
if (resp.ok) {
|
|
10500
|
+
if (this.debug) console.log(`${this.prefix} Shipped ${spans.length} spans`);
|
|
10501
|
+
return;
|
|
10502
|
+
}
|
|
10503
|
+
if (resp.status === 401) {
|
|
10504
|
+
console.warn(`${this.prefix} Invalid API key (401) \u2014 dropping ${spans.length} spans`);
|
|
10505
|
+
return;
|
|
10506
|
+
}
|
|
10507
|
+
if (resp.status < 500 && resp.status !== 429) {
|
|
10508
|
+
if (this.debug) console.warn(`${this.prefix} HTTP ${resp.status} \u2014 dropping spans`);
|
|
10509
|
+
return;
|
|
10510
|
+
}
|
|
10511
|
+
} catch (err) {
|
|
10512
|
+
if (this.debug) {
|
|
10513
|
+
console.warn(
|
|
10514
|
+
`${this.prefix} Attempt ${attempt}/${this.maxRetries}: ${err.message}`
|
|
10515
|
+
);
|
|
10516
|
+
}
|
|
10517
|
+
}
|
|
10518
|
+
if (attempt < this.maxRetries) {
|
|
10519
|
+
await new Promise((r) => setTimeout(r, 500 * 2 ** (attempt - 1)));
|
|
10520
|
+
}
|
|
10521
|
+
}
|
|
10522
|
+
console.warn(`${this.prefix} Failed to ship ${spans.length} spans after ${this.maxRetries} attempts`);
|
|
10523
|
+
}
|
|
10524
|
+
buildProtobuf(spans) {
|
|
10525
|
+
const protoSpans = spans.map((span2) => ({
|
|
10526
|
+
traceId: span2.traceId,
|
|
10527
|
+
spanId: span2.spanId,
|
|
10528
|
+
parentSpanId: span2.parentSpanId || void 0,
|
|
10529
|
+
name: span2.name,
|
|
10530
|
+
kind: span2.kind,
|
|
10531
|
+
startTimeUnixNano: nanoToLong(span2.startTimeUnixNano),
|
|
10532
|
+
endTimeUnixNano: nanoToLong(span2.endTimeUnixNano),
|
|
10533
|
+
attributes: span2.attributes.map((a) => ({
|
|
10534
|
+
key: a.key,
|
|
10535
|
+
value: a.value.intValue !== void 0 ? { intValue: nanoToLong(a.value.intValue) } : a.value
|
|
10536
|
+
})),
|
|
10537
|
+
status: span2.status ? { code: span2.status.code, message: span2.status.message } : void 0
|
|
10538
|
+
}));
|
|
10539
|
+
const resourceAttributes = [
|
|
10540
|
+
{ key: "service.name", value: { stringValue: "neatlogs.opencode" } },
|
|
10541
|
+
{ key: "service.version", value: { stringValue: __version__ } }
|
|
10542
|
+
];
|
|
10543
|
+
if (this.workflowName) {
|
|
10544
|
+
resourceAttributes.push({
|
|
10545
|
+
key: "neatlogs.workflow_name",
|
|
10546
|
+
value: { stringValue: this.workflowName }
|
|
10547
|
+
});
|
|
10548
|
+
}
|
|
10549
|
+
const message = {
|
|
10550
|
+
resourceSpans: [
|
|
10551
|
+
{
|
|
10552
|
+
resource: { attributes: resourceAttributes },
|
|
10553
|
+
scopeSpans: [
|
|
10554
|
+
{
|
|
10555
|
+
scope: { name: PACKAGE_NAME, version: __version__ },
|
|
10556
|
+
spans: protoSpans
|
|
10557
|
+
}
|
|
10558
|
+
]
|
|
10559
|
+
}
|
|
10560
|
+
]
|
|
10561
|
+
};
|
|
10562
|
+
const errMsg = ExportTraceServiceRequest.verify(message);
|
|
10563
|
+
if (errMsg && this.debug) console.warn(`${this.prefix} Proto verify: ${errMsg}`);
|
|
10564
|
+
return ExportTraceServiceRequest.encode(ExportTraceServiceRequest.fromObject(message)).finish();
|
|
10286
10565
|
}
|
|
10566
|
+
};
|
|
10567
|
+
function nanoToLong(nanoStr) {
|
|
10568
|
+
const big = BigInt(nanoStr);
|
|
10569
|
+
const low = Number(big & 0xffffffffn);
|
|
10570
|
+
const high = Number(big >> 32n & 0xffffffffn);
|
|
10571
|
+
return { low, high, unsigned: true };
|
|
10287
10572
|
}
|
|
10573
|
+
|
|
10574
|
+
// src/opencode-plugin.ts
|
|
10575
|
+
var DEFAULT_ENDPOINT = "https://staging-cloud.neatlogs.com";
|
|
10288
10576
|
var NeatlogsOpencodePlugin = async (_ctx) => {
|
|
10289
|
-
|
|
10290
|
-
const
|
|
10291
|
-
const
|
|
10577
|
+
const apiKey = (process.env.NEATLOGS_API_KEY ?? "").trim();
|
|
10578
|
+
const endpoint = (process.env.NEATLOGS_ENDPOINT ?? DEFAULT_ENDPOINT).trim();
|
|
10579
|
+
const workflowName = process.env.NEATLOGS_WORKFLOW_NAME || "opencode";
|
|
10292
10580
|
const captureSystemPrompt = String(process.env.NEATLOGS_CAPTURE_SYSTEM_PROMPT || "").toLowerCase() === "true";
|
|
10581
|
+
const debug = String(process.env.NEATLOGS_DEBUG || "").toLowerCase() === "true";
|
|
10582
|
+
const shipper = new TraceShipper({ apiKey, endpoint, workflowName, debug });
|
|
10583
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
10293
10584
|
function stateFor(sessionID) {
|
|
10294
10585
|
let s = sessions.get(sessionID);
|
|
10295
10586
|
if (!s) {
|
|
10296
|
-
s = {
|
|
10587
|
+
s = {
|
|
10588
|
+
traceId: generateTraceId(),
|
|
10589
|
+
toolStarts: /* @__PURE__ */ new Map(),
|
|
10590
|
+
outputParts: /* @__PURE__ */ new Map(),
|
|
10591
|
+
toolCalls: /* @__PURE__ */ new Map(),
|
|
10592
|
+
processed: /* @__PURE__ */ new Set(),
|
|
10593
|
+
currentInput: "",
|
|
10594
|
+
lastAssistantText: ""
|
|
10595
|
+
};
|
|
10297
10596
|
sessions.set(sessionID, s);
|
|
10298
10597
|
}
|
|
10299
10598
|
return s;
|
|
10300
10599
|
}
|
|
10600
|
+
function startRoot(st, sessionID) {
|
|
10601
|
+
if (st.rootSpan) return;
|
|
10602
|
+
st.rootSpan = {
|
|
10603
|
+
spanId: generateSpanId(),
|
|
10604
|
+
name: "opencode.session",
|
|
10605
|
+
startNano: nowNanoString(),
|
|
10606
|
+
attributes: [
|
|
10607
|
+
{ key: "neatlogs.span.kind", value: { stringValue: "AGENT" } },
|
|
10608
|
+
{ key: "neatlogs.agent.framework", value: { stringValue: "opencode" } },
|
|
10609
|
+
{ key: "neatlogs.conversation.id", value: { stringValue: sessionID } }
|
|
10610
|
+
]
|
|
10611
|
+
};
|
|
10612
|
+
}
|
|
10613
|
+
async function closeAndFlush(st, sessionID) {
|
|
10614
|
+
if (st.rootSpan) {
|
|
10615
|
+
const attrs = st.rootSpan.attributes.slice();
|
|
10616
|
+
setIO(attrs, "AGENT", st.currentInput || void 0, st.lastAssistantText || void 0);
|
|
10617
|
+
shipper.enqueue({
|
|
10618
|
+
traceId: st.traceId,
|
|
10619
|
+
spanId: st.rootSpan.spanId,
|
|
10620
|
+
name: st.rootSpan.name,
|
|
10621
|
+
kind: 1,
|
|
10622
|
+
startTimeUnixNano: st.rootSpan.startNano,
|
|
10623
|
+
endTimeUnixNano: nowNanoString(),
|
|
10624
|
+
attributes: attrs,
|
|
10625
|
+
status: { code: SpanStatusCode16.OK }
|
|
10626
|
+
});
|
|
10627
|
+
const m = nowNanoString();
|
|
10628
|
+
shipper.enqueue({
|
|
10629
|
+
traceId: st.traceId,
|
|
10630
|
+
spanId: generateSpanId(),
|
|
10631
|
+
parentSpanId: st.rootSpan.spanId,
|
|
10632
|
+
name: "neatlogs.trace.complete",
|
|
10633
|
+
kind: 1,
|
|
10634
|
+
startTimeUnixNano: m,
|
|
10635
|
+
endTimeUnixNano: m,
|
|
10636
|
+
attributes: [
|
|
10637
|
+
{ key: "neatlogs.trace.complete", value: { boolValue: true } },
|
|
10638
|
+
{ key: "neatlogs.internal", value: { boolValue: true } },
|
|
10639
|
+
{ key: "neatlogs.span.kind", value: { stringValue: "Neatlogs.INTERNAL" } }
|
|
10640
|
+
]
|
|
10641
|
+
});
|
|
10642
|
+
st.rootSpan = void 0;
|
|
10643
|
+
}
|
|
10644
|
+
await shipper.flush();
|
|
10645
|
+
void sessionID;
|
|
10646
|
+
}
|
|
10301
10647
|
return {
|
|
10302
|
-
/**
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10648
|
+
/** Fired when the user submits a prompt — open the turn's AGENT root. */
|
|
10649
|
+
"chat.message": async (_input, output) => {
|
|
10650
|
+
try {
|
|
10651
|
+
const sessionID = String(_input?.sessionID ?? output?.sessionID ?? "");
|
|
10652
|
+
if (!sessionID) return;
|
|
10653
|
+
const st = stateFor(sessionID);
|
|
10654
|
+
const parts = output?.parts ?? [];
|
|
10655
|
+
const text = Array.isArray(parts) ? parts.filter((p) => p?.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n") : "";
|
|
10656
|
+
if (text) st.currentInput = text;
|
|
10657
|
+
startRoot(st, sessionID);
|
|
10658
|
+
} catch {
|
|
10659
|
+
}
|
|
10660
|
+
},
|
|
10661
|
+
/** Capture the system prompt (opt-in). */
|
|
10662
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
10663
|
+
try {
|
|
10664
|
+
if (!captureSystemPrompt) return;
|
|
10665
|
+
const sessionID = String(_input?.sessionID ?? "");
|
|
10666
|
+
const parts = output?.system ?? output?.parts ?? output;
|
|
10667
|
+
const joined = Array.isArray(parts) ? parts.map((p) => typeof p === "string" ? p : p?.text ?? "").join("\n") : String(parts ?? "");
|
|
10668
|
+
if (sessionID && joined) stateFor(sessionID).systemPrompt = joined;
|
|
10669
|
+
} catch {
|
|
10670
|
+
}
|
|
10671
|
+
},
|
|
10672
|
+
/** Global event bus — message parts, assistant completions, session idle. */
|
|
10307
10673
|
event: async ({ event }) => {
|
|
10308
10674
|
try {
|
|
10309
|
-
await handleEvent2(
|
|
10675
|
+
await handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, event);
|
|
10310
10676
|
} catch {
|
|
10311
10677
|
}
|
|
10312
10678
|
},
|
|
10313
|
-
/**
|
|
10679
|
+
/** Tool start — record start time + args (span built atomically in `after`). */
|
|
10314
10680
|
"tool.execute.before": async (input, output) => {
|
|
10315
10681
|
try {
|
|
10316
|
-
const sessionID = input?.sessionID ?? "";
|
|
10317
|
-
|
|
10682
|
+
const sessionID = String(input?.sessionID ?? "");
|
|
10683
|
+
if (!sessionID) return;
|
|
10318
10684
|
const st = stateFor(sessionID);
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
}
|
|
10328
|
-
},
|
|
10329
|
-
otelContext15.active()
|
|
10330
|
-
);
|
|
10331
|
-
if (callID) st.toolSpans.set(String(callID), span2);
|
|
10685
|
+
startRoot(st, sessionID);
|
|
10686
|
+
const callID = String(input?.callID ?? input?.tool ?? "");
|
|
10687
|
+
st.toolStarts.set(callID, {
|
|
10688
|
+
spanId: generateSpanId(),
|
|
10689
|
+
startNano: nowNanoString(),
|
|
10690
|
+
tool: String(input?.tool ?? "tool"),
|
|
10691
|
+
args: output?.args
|
|
10692
|
+
});
|
|
10332
10693
|
} catch {
|
|
10333
10694
|
}
|
|
10334
10695
|
},
|
|
10335
|
-
/**
|
|
10336
|
-
"tool.execute.after": async (input,
|
|
10696
|
+
/** Tool end — enqueue the completed TOOL span (parented to the turn root). */
|
|
10697
|
+
"tool.execute.after": async (input, result) => {
|
|
10337
10698
|
try {
|
|
10338
|
-
const sessionID = input?.sessionID ?? "";
|
|
10339
|
-
|
|
10699
|
+
const sessionID = String(input?.sessionID ?? "");
|
|
10700
|
+
if (!sessionID) return;
|
|
10340
10701
|
const st = stateFor(sessionID);
|
|
10341
|
-
const
|
|
10342
|
-
|
|
10343
|
-
if (
|
|
10344
|
-
|
|
10702
|
+
const callID = String(input?.callID ?? input?.tool ?? "");
|
|
10703
|
+
const start = st.toolStarts.get(callID);
|
|
10704
|
+
if (!start) return;
|
|
10705
|
+
st.toolStarts.delete(callID);
|
|
10706
|
+
const attrs = [
|
|
10707
|
+
{ key: "neatlogs.span.kind", value: { stringValue: "TOOL" } },
|
|
10708
|
+
{ key: "neatlogs.tool.name", value: { stringValue: start.tool } },
|
|
10709
|
+
{ key: "neatlogs.conversation.id", value: { stringValue: sessionID } }
|
|
10710
|
+
];
|
|
10711
|
+
if (start.args !== void 0) {
|
|
10712
|
+
setIO(attrs, "TOOL", safeStringify15(start.args), void 0);
|
|
10713
|
+
push(attrs, attrStr("neatlogs.tool.input", safeStringify15(start.args)));
|
|
10714
|
+
}
|
|
10715
|
+
if (result?.title) push(attrs, attrStr("neatlogs.tool.title", String(result.title)));
|
|
10716
|
+
const out = result?.output ?? result?.result;
|
|
10345
10717
|
if (out !== void 0) {
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10718
|
+
const o = typeof out === "string" ? out : safeStringify15(out);
|
|
10719
|
+
setIO(attrs, "TOOL", void 0, o);
|
|
10720
|
+
push(attrs, attrStr("neatlogs.tool.output", o));
|
|
10721
|
+
}
|
|
10722
|
+
if (result?.metadata !== void 0) {
|
|
10723
|
+
push(attrs, attrStr("neatlogs.tool.metadata", safeStringify15(result.metadata)));
|
|
10724
|
+
}
|
|
10725
|
+
shipper.enqueue({
|
|
10726
|
+
traceId: st.traceId,
|
|
10727
|
+
spanId: start.spanId,
|
|
10728
|
+
parentSpanId: st.rootSpan?.spanId,
|
|
10729
|
+
name: `opencode.tool.${start.tool}`,
|
|
10730
|
+
kind: 1,
|
|
10731
|
+
startTimeUnixNano: start.startNano,
|
|
10732
|
+
endTimeUnixNano: nowNanoString(),
|
|
10733
|
+
attributes: attrs,
|
|
10734
|
+
status: { code: SpanStatusCode16.OK }
|
|
10735
|
+
});
|
|
10354
10736
|
} catch {
|
|
10355
10737
|
}
|
|
10356
10738
|
}
|
|
10357
10739
|
};
|
|
10358
10740
|
};
|
|
10359
|
-
function handleEvent2(
|
|
10741
|
+
function handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, event) {
|
|
10360
10742
|
const type = event?.type;
|
|
10361
10743
|
const props = event?.properties ?? {};
|
|
10362
10744
|
if (type === "message.part.updated" || type === "message.part.completed") {
|
|
10363
10745
|
const part = props.part ?? props;
|
|
10364
10746
|
const messageID = part?.messageID ?? part?.message_id;
|
|
10365
10747
|
const sessionID = part?.sessionID ?? props.sessionID;
|
|
10366
|
-
if (
|
|
10367
|
-
|
|
10368
|
-
|
|
10748
|
+
if (!messageID || !sessionID) return void 0;
|
|
10749
|
+
const st = stateFor(String(sessionID));
|
|
10750
|
+
if (part?.type === "text" && typeof part?.text === "string") {
|
|
10751
|
+
st.outputParts.set(String(messageID), part.text);
|
|
10752
|
+
} else if (part?.type === "tool" && part?.tool) {
|
|
10753
|
+
const list = st.toolCalls.get(String(messageID)) ?? [];
|
|
10754
|
+
const callID = String(part.callID ?? part.tool);
|
|
10755
|
+
const input = part?.state?.input ?? {};
|
|
10756
|
+
const existing = list.find((t) => t.callID === callID);
|
|
10757
|
+
if (existing) existing.input = input;
|
|
10758
|
+
else list.push({ name: String(part.tool), input, callID });
|
|
10759
|
+
st.toolCalls.set(String(messageID), list);
|
|
10369
10760
|
}
|
|
10370
|
-
return;
|
|
10761
|
+
return void 0;
|
|
10371
10762
|
}
|
|
10372
10763
|
if (type === "message.updated" || type === "message.completed") {
|
|
10373
10764
|
const info = props.info ?? props.message ?? props;
|
|
10374
10765
|
const sessionID = info?.sessionID ?? info?.session_id;
|
|
10375
|
-
if (!sessionID) return;
|
|
10766
|
+
if (!sessionID) return void 0;
|
|
10767
|
+
if (info?.role !== "assistant") return void 0;
|
|
10768
|
+
const completed = info?.time?.completed ?? info?.completed;
|
|
10769
|
+
const id = String(info?.id ?? "");
|
|
10770
|
+
if (!completed || !id) return void 0;
|
|
10376
10771
|
const st = stateFor(String(sessionID));
|
|
10377
|
-
if (
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
if (info?.role === "assistant") {
|
|
10383
|
-
const completed = info?.time?.completed ?? info?.completed;
|
|
10384
|
-
const id = String(info?.id ?? "");
|
|
10385
|
-
if (!completed || st.emitted.has(id)) return;
|
|
10386
|
-
st.emitted.add(id);
|
|
10387
|
-
emitLlmSpan2(tracer, st, captureSystemPrompt, info, String(sessionID));
|
|
10388
|
-
}
|
|
10389
|
-
return;
|
|
10772
|
+
if (st.processed.has(id)) return void 0;
|
|
10773
|
+
st.processed.add(id);
|
|
10774
|
+
startRoot(st, String(sessionID));
|
|
10775
|
+
emitLlmSpan2(shipper, st, info, String(sessionID));
|
|
10776
|
+
return shipper.flush().catch(() => void 0);
|
|
10390
10777
|
}
|
|
10391
10778
|
if (type === "session.idle" || type === "session.deleted") {
|
|
10392
10779
|
const sessionID = props.sessionID ?? props.info?.id;
|
|
10393
10780
|
if (!sessionID) return void 0;
|
|
10394
10781
|
const st = sessions.get(String(sessionID));
|
|
10395
10782
|
if (!st) return void 0;
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
ts.end();
|
|
10399
|
-
} catch {
|
|
10400
|
-
}
|
|
10401
|
-
}
|
|
10402
|
-
st.toolSpans.clear();
|
|
10783
|
+
st.toolStarts.clear();
|
|
10784
|
+
const p = closeAndFlush(st, String(sessionID));
|
|
10403
10785
|
if (type === "session.deleted") sessions.delete(String(sessionID));
|
|
10404
|
-
return
|
|
10786
|
+
return p;
|
|
10405
10787
|
}
|
|
10406
10788
|
return void 0;
|
|
10407
10789
|
}
|
|
10408
|
-
function emitLlmSpan2(
|
|
10790
|
+
function emitLlmSpan2(shipper, st, info, sessionID) {
|
|
10409
10791
|
const model = info?.modelID ?? info?.model ?? "";
|
|
10410
10792
|
const provider = info?.providerID ?? info?.provider ?? "";
|
|
10411
|
-
const attrs =
|
|
10412
|
-
"neatlogs.span.kind": "LLM",
|
|
10413
|
-
"neatlogs.conversation.id": sessionID
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
attrs
|
|
10420
|
-
attrs
|
|
10421
|
-
}
|
|
10422
|
-
let inIdx = captureSystemPrompt && info?.system ? 1 : 0;
|
|
10423
|
-
if (st.lastUserText) {
|
|
10424
|
-
attrs[`neatlogs.llm.input_messages.${inIdx}.role`] = "user";
|
|
10425
|
-
attrs[`neatlogs.llm.input_messages.${inIdx}.content`] = st.lastUserText;
|
|
10793
|
+
const attrs = [
|
|
10794
|
+
{ key: "neatlogs.span.kind", value: { stringValue: "LLM" } },
|
|
10795
|
+
{ key: "neatlogs.conversation.id", value: { stringValue: sessionID } }
|
|
10796
|
+
];
|
|
10797
|
+
push(attrs, attrStr("neatlogs.llm.model_name", model ? String(model) : void 0));
|
|
10798
|
+
push(attrs, attrStr("neatlogs.llm.provider", provider ? String(provider) : void 0));
|
|
10799
|
+
let inIdx = 0;
|
|
10800
|
+
if (st.systemPrompt) {
|
|
10801
|
+
push(attrs, attrStr(`neatlogs.llm.input_messages.${inIdx}.role`, "system"));
|
|
10802
|
+
push(attrs, attrStr(`neatlogs.llm.input_messages.${inIdx}.content`, st.systemPrompt));
|
|
10426
10803
|
inIdx++;
|
|
10427
10804
|
}
|
|
10428
|
-
|
|
10805
|
+
if (st.currentInput) {
|
|
10806
|
+
push(attrs, attrStr(`neatlogs.llm.input_messages.${inIdx}.role`, "user"));
|
|
10807
|
+
push(attrs, attrStr(`neatlogs.llm.input_messages.${inIdx}.content`, st.currentInput));
|
|
10808
|
+
setIO(attrs, "LLM", st.currentInput, void 0);
|
|
10809
|
+
}
|
|
10810
|
+
const outText = st.outputParts.get(String(info?.id)) || messageText2(info) || "";
|
|
10811
|
+
const toolCalls = st.toolCalls.get(String(info?.id)) ?? [];
|
|
10429
10812
|
if (outText) {
|
|
10430
|
-
attrs
|
|
10431
|
-
attrs
|
|
10813
|
+
push(attrs, attrStr("neatlogs.llm.output_messages.0.role", "assistant"));
|
|
10814
|
+
push(attrs, attrStr("neatlogs.llm.output_messages.0.content", outText));
|
|
10815
|
+
setIO(attrs, "LLM", void 0, outText);
|
|
10816
|
+
st.lastAssistantText = outText;
|
|
10817
|
+
}
|
|
10818
|
+
if (toolCalls.length) {
|
|
10819
|
+
toolCalls.forEach((tc, j) => {
|
|
10820
|
+
push(attrs, attrStr(`neatlogs.llm.tool_calls.${j}.name`, tc.name));
|
|
10821
|
+
push(attrs, attrStr(`neatlogs.llm.tool_calls.${j}.arguments`, safeStringify15(tc.input ?? {})));
|
|
10822
|
+
});
|
|
10823
|
+
if (!outText) {
|
|
10824
|
+
const rendered = toolCalls.map((tc) => `\u2192 ${tc.name}(${safeStringify15(tc.input ?? {})})`).join("\n");
|
|
10825
|
+
push(attrs, attrStr("neatlogs.llm.output_messages.0.role", "assistant"));
|
|
10826
|
+
push(attrs, attrStr("neatlogs.llm.output_messages.0.content", rendered));
|
|
10827
|
+
setIO(attrs, "LLM", void 0, rendered);
|
|
10828
|
+
}
|
|
10432
10829
|
}
|
|
10433
10830
|
const tokens = info?.tokens;
|
|
10434
10831
|
if (tokens) {
|
|
10435
|
-
|
|
10436
|
-
|
|
10832
|
+
push(attrs, attrInt("neatlogs.llm.token_count.prompt", tokens.input));
|
|
10833
|
+
push(attrs, attrInt("neatlogs.llm.token_count.completion", tokens.output));
|
|
10437
10834
|
if (tokens.input != null && tokens.output != null) {
|
|
10438
|
-
attrs
|
|
10439
|
-
}
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10835
|
+
push(attrs, attrInt("neatlogs.llm.token_count.total", tokens.input + tokens.output));
|
|
10836
|
+
}
|
|
10837
|
+
push(attrs, attrInt("neatlogs.llm.token_count.reasoning", tokens.reasoning));
|
|
10838
|
+
push(attrs, attrInt("neatlogs.llm.token_count.cache_read", tokens.cache?.read));
|
|
10839
|
+
push(attrs, attrInt("neatlogs.llm.token_count.cache_write", tokens.cache?.write));
|
|
10840
|
+
}
|
|
10841
|
+
push(attrs, attrDouble("neatlogs.llm.cost_usd", info?.cost));
|
|
10842
|
+
const createdMs = info?.time?.created;
|
|
10843
|
+
const startNano = typeof createdMs === "number" ? msToNanoString(Math.floor(createdMs)) : nowNanoString();
|
|
10844
|
+
shipper.enqueue({
|
|
10845
|
+
traceId: st.traceId,
|
|
10846
|
+
spanId: generateSpanId(),
|
|
10847
|
+
parentSpanId: st.rootSpan?.spanId,
|
|
10848
|
+
name: `opencode.llm.${model || "model"}`,
|
|
10849
|
+
kind: 1,
|
|
10850
|
+
startTimeUnixNano: startNano,
|
|
10851
|
+
endTimeUnixNano: nowNanoString(),
|
|
10852
|
+
attributes: attrs,
|
|
10853
|
+
status: { code: SpanStatusCode16.OK }
|
|
10854
|
+
});
|
|
10855
|
+
if (info?.id) {
|
|
10856
|
+
st.outputParts.delete(String(info.id));
|
|
10857
|
+
st.toolCalls.delete(String(info.id));
|
|
10858
|
+
}
|
|
10859
|
+
}
|
|
10860
|
+
function push(arr, kv) {
|
|
10861
|
+
if (kv) arr.push(kv);
|
|
10862
|
+
}
|
|
10863
|
+
function setIO(arr, kind, input, output) {
|
|
10864
|
+
const k = kind.toLowerCase();
|
|
10865
|
+
if (input !== void 0) {
|
|
10866
|
+
push(arr, attrStr("input.value", input));
|
|
10867
|
+
push(arr, attrStr("neatlogs.input.value", input));
|
|
10868
|
+
push(arr, attrStr(`neatlogs.${k}.input`, input));
|
|
10869
|
+
}
|
|
10870
|
+
if (output !== void 0) {
|
|
10871
|
+
push(arr, attrStr("output.value", output));
|
|
10872
|
+
push(arr, attrStr("neatlogs.output.value", output));
|
|
10873
|
+
push(arr, attrStr(`neatlogs.${k}.output`, output));
|
|
10443
10874
|
}
|
|
10444
|
-
if (info?.cost != null) attrs["neatlogs.llm.cost_usd"] = info.cost;
|
|
10445
|
-
const span2 = tracer.startSpan(`opencode.llm.${model || "model"}`, { attributes: attrs }, otelContext15.active());
|
|
10446
|
-
span2.setStatus({ code: SpanStatusCode16.OK });
|
|
10447
|
-
span2.end();
|
|
10448
|
-
if (info?.id) st.messageText.delete(String(info.id));
|
|
10449
10875
|
}
|
|
10450
10876
|
function messageText2(info) {
|
|
10451
10877
|
if (!info) return "";
|
|
@@ -10465,7 +10891,7 @@ function safeStringify15(value) {
|
|
|
10465
10891
|
try {
|
|
10466
10892
|
return JSON.stringify(value) ?? "";
|
|
10467
10893
|
} catch {
|
|
10468
|
-
return
|
|
10894
|
+
return String(value);
|
|
10469
10895
|
}
|
|
10470
10896
|
}
|
|
10471
10897
|
|