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