neatlogs 1.1.9 → 1.1.11

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 CHANGED
@@ -6009,7 +6009,7 @@ function _resetMastraCache() {
6009
6009
  }
6010
6010
 
6011
6011
  // src/version.ts
6012
- var __version__ = "1.1.9";
6012
+ var __version__ = "1.1.11";
6013
6013
 
6014
6014
  // src/init.ts
6015
6015
  var logger13 = getLogger();
@@ -9621,6 +9621,7 @@ function patchModelInPlace(model) {
9621
9621
  if (isStream) attrs["neatlogs.llm.is_streaming"] = true;
9622
9622
  const promptInput = callOpts?.prompt ?? callOpts?.messages;
9623
9623
  if (promptInput !== void 0) attrs["input.value"] = safeStringify11(promptInput);
9624
+ setModelInputMessages(attrs, promptInput);
9624
9625
  captureInvocationParams(attrs, callOpts);
9625
9626
  const spanName = `mastra.llm.${modelId || "model"}.${fn}`;
9626
9627
  if (isStream) {
@@ -10161,6 +10162,20 @@ function captureInvocationParams(attrs, callOpts) {
10161
10162
  attrs["neatlogs.llm.invocation_parameters"] = safeStringify11(invocation);
10162
10163
  }
10163
10164
  }
10165
+ function setModelInputMessages(attrs, promptInput) {
10166
+ if (!Array.isArray(promptInput)) return;
10167
+ for (let i = 0; i < promptInput.length; i++) {
10168
+ const msg = promptInput[i];
10169
+ if (!msg || typeof msg !== "object") continue;
10170
+ attrs[`neatlogs.llm.input_messages.${i}.role`] = msg.role ?? "user";
10171
+ attrs[`neatlogs.llm.input_messages.${i}.content`] = messagePartsToText(msg.content);
10172
+ }
10173
+ }
10174
+ function messagePartsToText(content) {
10175
+ if (typeof content === "string") return content;
10176
+ if (!Array.isArray(content)) return "";
10177
+ return content.filter((p) => p?.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
10178
+ }
10164
10179
  function setToolCall(span2, i, name, args, id) {
10165
10180
  span2.setAttribute(`neatlogs.llm.tool_calls.${i}.name`, name ?? "");
10166
10181
  span2.setAttribute(`neatlogs.llm.tool_calls.${i}.arguments`, safeStringify11(args ?? {}));
@@ -11278,6 +11293,9 @@ var TraceShipper = class {
11278
11293
  workflowName;
11279
11294
  queue = [];
11280
11295
  prefix = "[neatlogs/opencode]";
11296
+ /** In-flight flush round-trips — awaited by `settle()` on dispose so a
11297
+ * short-lived host (`opencode run`) can force pending exports to complete. */
11298
+ inflight = /* @__PURE__ */ new Set();
11281
11299
  constructor(opts) {
11282
11300
  this.apiKey = opts.apiKey;
11283
11301
  this.endpoint = opts.endpoint.endsWith("/") ? opts.endpoint.slice(0, -1) : opts.endpoint;
@@ -11295,8 +11313,26 @@ var TraceShipper = class {
11295
11313
  * Ship all queued spans in a single awaited POST. Resolves only once the HTTP
11296
11314
  * response is received (or all retries are exhausted) — so a short-lived host
11297
11315
  * can safely exit immediately after awaiting this.
11316
+ *
11317
+ * The returned promise is also tracked in `inflight` so `settle()` can await
11318
+ * it even when the caller (an un-awaited opencode `event` hook) drops it.
11298
11319
  */
11299
- async flush() {
11320
+ flush() {
11321
+ const p = this._flush().finally(() => this.inflight.delete(p));
11322
+ this.inflight.add(p);
11323
+ return p;
11324
+ }
11325
+ /**
11326
+ * Await every flush started so far (including ones whose promise the caller
11327
+ * discarded). Called from the plugin's `dispose` hook, which opencode DOES
11328
+ * await on scope teardown — unlike the fire-and-forget `event` hook.
11329
+ */
11330
+ async settle() {
11331
+ while (this.inflight.size > 0) {
11332
+ await Promise.allSettled(Array.from(this.inflight));
11333
+ }
11334
+ }
11335
+ async _flush() {
11300
11336
  if (this.queue.length === 0) return;
11301
11337
  if (!this.apiKey) {
11302
11338
  this.queue = [];
@@ -11472,6 +11508,22 @@ var NeatlogsOpencodePlugin = async (_ctx) => {
11472
11508
  void sessionID;
11473
11509
  }
11474
11510
  return {
11511
+ /**
11512
+ * Awaited by opencode on scope teardown (registered via an internal
11513
+ * `addFinalizer`), unlike the fire-and-forget `event` hook. On a short-lived
11514
+ * `opencode run`, `session.idle`'s `await shipper.flush()` is a dangling
11515
+ * promise the process kills before the POST resolves — so we close any turn
11516
+ * still open and `settle()` all in-flight flushes here, before exit.
11517
+ */
11518
+ dispose: async () => {
11519
+ try {
11520
+ for (const [sessionID, st] of sessions) {
11521
+ if (st.rootSpan) await closeAndFlush(st, sessionID);
11522
+ }
11523
+ await shipper.settle();
11524
+ } catch {
11525
+ }
11526
+ },
11475
11527
  /** Fired when the user submits a prompt — open the turn's AGENT root. */
11476
11528
  "chat.message": async (_input, output) => {
11477
11529
  try {
@@ -11668,6 +11720,7 @@ function emitLlmSpan2(shipper, st, info, sessionID) {
11668
11720
  push(attrs, attrInt("neatlogs.llm.token_count.cache_write", tokens.cache?.write));
11669
11721
  }
11670
11722
  push(attrs, attrDouble("neatlogs.llm.cost_usd", info?.cost));
11723
+ push(attrs, attrStr("neatlogs.llm.finish_reason", info?.finish ? String(info.finish) : void 0));
11671
11724
  const createdMs = info?.time?.created;
11672
11725
  const startNano = typeof createdMs === "number" ? msToNanoString(Math.floor(createdMs)) : nowNanoString();
11673
11726
  shipper.enqueue({