@amaster.ai/dsh-langfuse 0.1.25 → 0.1.27

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/lib/client.js CHANGED
@@ -252,18 +252,23 @@ export class LangfuseReporter {
252
252
  if (!tracing || !api)
253
253
  return null;
254
254
  try {
255
- const parent = parentContext(input.context?.traceparent);
255
+ const externalParent = parentContext(input.context?.traceparent);
256
+ const parent = externalParent === NO_PARENT && input.traceId
257
+ ? { traceId: input.traceId, spanId: '0000000000000001', traceFlags: 1 }
258
+ : externalParent;
256
259
  input = this.mask(input);
257
- const root = api.context.with(api.ROOT_CONTEXT, () => tracing.startObservation(input.name, { metadata: input.metadata }, { parentSpanContext: parent }));
260
+ const root = api.context.with(api.ROOT_CONTEXT, () => tracing.startObservation(input.name, { metadata: input.metadata }, { parentSpanContext: parent, startTime: input.startTime }));
258
261
  this.stampTraceContext(root, {
259
262
  sessionId: input.sessionId,
260
- traceName: parent === NO_PARENT ? input.name : undefined,
263
+ traceName: externalParent === NO_PARENT ? input.name : undefined,
261
264
  userId: input.context?.userId ?? this.config.userId,
262
265
  tags: input.context?.tags ?? this.config.tags,
263
266
  });
264
267
  // External traces retain their owner's trace name and trace-level IO.
265
- if (parent === NO_PARENT)
268
+ if (externalParent === NO_PARENT) {
266
269
  this.roots.add(root);
270
+ root.otelSpan.setAttribute(tracing.LangfuseOtelSpanAttributes.IS_APP_ROOT, true);
271
+ }
267
272
  else
268
273
  root.otelSpan.setAttribute(tracing.LangfuseOtelSpanAttributes.IS_APP_ROOT, false);
269
274
  return root;
@@ -273,6 +278,25 @@ export class LangfuseReporter {
273
278
  return null;
274
279
  }
275
280
  }
281
+ /** A running task keeps its trace identity; late host context is a link. */
282
+ updateContext(span, context) {
283
+ if (!span)
284
+ return;
285
+ try {
286
+ const previous = this.traceContext.get(span);
287
+ this.stampTraceContext(span, {
288
+ ...previous,
289
+ userId: context.userId ?? previous?.userId,
290
+ tags: context.tags ?? previous?.tags,
291
+ });
292
+ const parent = parentContext(context.traceparent);
293
+ if (parent !== NO_PARENT)
294
+ span.otelSpan.addLink({ context: parent });
295
+ }
296
+ catch (error) {
297
+ console.error('[dsh-langfuse] context update failed:', error);
298
+ }
299
+ }
276
300
  /** Open a generation (one per LLM call) under any observation parent. */
277
301
  startGeneration(parent, input) {
278
302
  const api = this.api;
@@ -327,9 +351,14 @@ export class LangfuseReporter {
327
351
  input: input.input,
328
352
  metadata: input.metadata,
329
353
  };
330
- const span = api.context.with(api.ROOT_CONTEXT, () => input.asType === 'tool'
331
- ? parent.startObservation(input.name, attributes, { asType: 'tool' })
332
- : parent.startObservation(input.name, attributes));
354
+ const span = api.context.with(api.ROOT_CONTEXT, () => input.startTime && this.tracing
355
+ ? this.tracing.startObservation(input.name, attributes, {
356
+ parentSpanContext: parent.otelSpan.spanContext(),
357
+ startTime: input.startTime,
358
+ })
359
+ : input.asType === 'tool'
360
+ ? parent.startObservation(input.name, attributes, { asType: 'tool' })
361
+ : parent.startObservation(input.name, attributes));
333
362
  this.stampTraceContext(span, this.traceContext.get(parent));
334
363
  return span;
335
364
  }
package/lib/index.js CHANGED
@@ -16,6 +16,8 @@
16
16
  * (`turn/start` opens a root span — in the v5 SDK the root observation IS
17
17
  * the trace — the turn's first `user/message` sets the input, `turn/end`
18
18
  * stamps the end reason and ends the root, which is what exports it).
19
+ * - Native A2A inbox tasks own a trace spanning admission through task termination;
20
+ * agent turns and queue controls are children of that task.
19
21
  * - subagent child sessions (`session/created` with `header.parentSession`)
20
22
  * ride the parent's trace: a `subagent` **span** opened under the enclosing
21
23
  * delegation tool span, the child's generations/tool spans nested under it,
@@ -28,6 +30,7 @@
28
30
  *
29
31
  * @module dsh-langfuse
30
32
  */
33
+ import { createHash } from 'node:crypto';
31
34
  import Schema from '@deepseek-ai/schemastery';
32
35
  import { LangfuseReporter } from './client.js';
33
36
  import { isHookInvoked, isHookResult } from './hooks.js';
@@ -203,6 +206,10 @@ export function apply(ctx, config) {
203
206
  return;
204
207
  const reporter = new LangfuseReporter(config);
205
208
  const sessions = new Map();
209
+ const nativeInboxes = new Set();
210
+ const pendingTurns = new Map();
211
+ const inboxInputs = new Map();
212
+ const inboxTasks = new Map();
206
213
  const children = new Map();
207
214
  const completedTurns = new Map();
208
215
  function rememberTurn(sessionId, turn, parent, turnTrace) {
@@ -268,11 +275,98 @@ export function apply(ctx, config) {
268
275
  tags: context.tags ? [...context.tags] : undefined,
269
276
  });
270
277
  });
271
- function openTrace(sessionId, metadata) {
278
+ function openTrace(sessionId, metadata, startTime) {
272
279
  const context = sessionId ? traceContexts.get(sessionId) : undefined;
273
280
  if (sessionId)
274
281
  traceContexts.delete(sessionId);
275
- return reporter.openTrace({ name: config.traceName, sessionId, metadata, context });
282
+ return reporter.openTrace({ name: config.traceName, sessionId, metadata, context, startTime });
283
+ }
284
+ const inboxKey = (contextId, taskId) => JSON.stringify([contextId, taskId]);
285
+ function taskTrace(event) {
286
+ if (!event.taskId)
287
+ return;
288
+ const key = inboxKey(event.contextId, event.taskId);
289
+ let task = inboxTasks.get(key);
290
+ if (!task) {
291
+ const context = traceContexts.get(event.contextId);
292
+ traceContexts.delete(event.contextId);
293
+ task = {
294
+ key,
295
+ contextId: event.contextId,
296
+ taskId: event.taskId,
297
+ trace: reporter.openTrace({
298
+ name: config.traceName === 'dsh-turn' ? 'a2a-task' : config.traceName,
299
+ sessionId: event.contextId,
300
+ metadata: { taskId: event.taskId, taskState: 'queued' },
301
+ traceId: createHash('sha256').update(key).digest('hex').slice(0, 32),
302
+ startTime: new Date(event.queuedAt ?? event.at),
303
+ }),
304
+ hasInput: false,
305
+ messageIds: new Set(),
306
+ nativeMessageIds: new Set(),
307
+ openTurns: 0,
308
+ };
309
+ if (context)
310
+ reporter.updateContext(task.trace, context);
311
+ inboxTasks.set(key, task);
312
+ }
313
+ if (event.messageId)
314
+ task.messageIds.add(event.messageId);
315
+ if (event.nativeMessageId)
316
+ task.nativeMessageIds.add(event.nativeMessageId);
317
+ if (!task.hasInput && config.captureContent && event.input) {
318
+ reporter.updateSpan(task.trace, { input: event.input });
319
+ task.hasInput = true;
320
+ }
321
+ reporter.updateSpan(task.trace, {
322
+ metadata: {
323
+ referenceTaskIds: event.referenceTaskIds?.length ? event.referenceTaskIds : undefined,
324
+ a2aMessageIds: [...task.messageIds],
325
+ nativeMessageIds: [...task.nativeMessageIds],
326
+ },
327
+ });
328
+ return task;
329
+ }
330
+ function closeTask(task, endedAt = new Date()) {
331
+ if (!task.end || task.openTurns)
332
+ return;
333
+ const end = task.end;
334
+ if (inboxInputs.get(task.contextId)?.[0]?.taskId === task.taskId)
335
+ inboxInputs.delete(task.contextId);
336
+ if (inboxTasks.get(task.key) === task)
337
+ inboxTasks.delete(task.key);
338
+ afterMedia(task.trace, (at) => reporter.endSpan(task.trace, {
339
+ output: end.phase === 'completed' && config.captureContent && task.lastOutput !== undefined
340
+ ? task.lastOutput
341
+ : end.output,
342
+ level: end.level,
343
+ metadata: { taskState: end.phase },
344
+ }, at), new Date(Math.max(end.at, endedAt.getTime())));
345
+ }
346
+ function endTurn(state, update = {}, endedAt) {
347
+ const task = state.taskKey ? inboxTasks.get(state.taskKey) : undefined;
348
+ if (task && state.lastAssistantText !== undefined)
349
+ task.lastOutput = state.lastAssistantText;
350
+ afterMedia(state.trace, (at) => {
351
+ reporter.endSpan(state.trace, update, at);
352
+ if (task) {
353
+ task.openTurns--;
354
+ closeTask(task, at);
355
+ }
356
+ }, endedAt);
357
+ }
358
+ function suspendTasks(contextId) {
359
+ for (const task of [...inboxTasks.values()]) {
360
+ if (contextId !== undefined && task.contextId !== contextId)
361
+ continue;
362
+ task.end ??= {
363
+ phase: 'suspended',
364
+ at: Date.now(),
365
+ output: { state: 'suspended' },
366
+ level: 'DEFAULT',
367
+ };
368
+ closeTask(task);
369
+ }
276
370
  }
277
371
  function rootSessionId(sessionId) {
278
372
  while (sessionId && children.has(sessionId))
@@ -314,10 +408,36 @@ export function apply(ctx, config) {
314
408
  const existing = id ? sessions.get(id) : undefined;
315
409
  if (existing)
316
410
  return existing.trace;
317
- const trace = openTrace(id);
411
+ const pending = id ? pendingTurns.get(id) : undefined;
412
+ const turn = pending?.turn;
413
+ if (id && nativeInboxes.has(id)) {
414
+ const selected = inboxInputs.get(id)?.[0];
415
+ const task = selected ? taskTrace(selected) : undefined;
416
+ if (!task || task.end)
417
+ return null;
418
+ const context = traceContexts.get(id);
419
+ if (context) {
420
+ reporter.updateContext(task.trace, context);
421
+ traceContexts.delete(id);
422
+ }
423
+ const trace = reporter.startSpan(task.trace, {
424
+ name: 'agent:turn',
425
+ metadata: { turn, taskId: task.taskId },
426
+ startTime: pending?.startTime,
427
+ });
428
+ pendingTurns.delete(id);
429
+ if (trace) {
430
+ task.openTurns++;
431
+ sessions.set(id, { trace, hasInput: turn === undefined, turn, taskKey: task.key });
432
+ }
433
+ return trace;
434
+ }
435
+ const trace = openTrace(id, turn === undefined ? undefined : { turn }, pending?.startTime);
436
+ if (id)
437
+ pendingTurns.delete(id);
318
438
  // hasInput starts true: one-offs never take a turn's first-message input.
319
439
  if (trace && id)
320
- sessions.set(id, { trace, hasInput: true });
440
+ sessions.set(id, { trace, hasInput: turn === undefined, turn });
321
441
  return trace;
322
442
  }
323
443
  /**
@@ -328,6 +448,129 @@ export function apply(ctx, config) {
328
448
  const child = sessionId ? children.get(sessionId) : undefined;
329
449
  return child?.span ?? traceFor(sessionId);
330
450
  }
451
+ ctx.on('a2a/inbox', (event) => {
452
+ try {
453
+ const id = event.contextId;
454
+ nativeInboxes.add(id);
455
+ if (event.phase === 'resolved')
456
+ return;
457
+ const metadata = {
458
+ taskId: event.taskId,
459
+ taskIds: event.taskIds,
460
+ messageId: event.messageId,
461
+ nativeMessageId: event.nativeMessageId,
462
+ pending: event.pending,
463
+ target: event.target,
464
+ position: event.position,
465
+ agentStatus: event.agentStatus,
466
+ turn: event.turn,
467
+ step: event.step,
468
+ };
469
+ const input = config.captureContent ? event.input : undefined;
470
+ const waitMs = event.queuedAt === undefined
471
+ ? undefined
472
+ : Math.max(0, (event.selectedAt ?? event.at) - event.queuedAt);
473
+ const output = {
474
+ state: event.phase,
475
+ target: event.target,
476
+ position: event.position,
477
+ pending: event.pending,
478
+ agentStatus: event.agentStatus,
479
+ };
480
+ if (event.phase === 'selected') {
481
+ const task = taskTrace(event);
482
+ if (task && task.selectedAt === undefined) {
483
+ task.selectedAt = event.selectedAt ?? event.at;
484
+ task.waitMs = waitMs;
485
+ }
486
+ if (task)
487
+ reporter.updateSpan(task.trace, {
488
+ metadata: { taskState: 'running', waitMs: task.waitMs },
489
+ });
490
+ const inputs = inboxInputs.get(id) ?? [];
491
+ if (!inputs.some((input) => input.nativeMessageId === event.nativeMessageId))
492
+ inputs.push(event);
493
+ inboxInputs.set(id, inputs);
494
+ return;
495
+ }
496
+ if (['prepared', 'prepare-failed', 'prepare-aborted'].includes(event.phase)) {
497
+ const parent = parentFor(id);
498
+ const inputs = inboxInputs.get(id) ?? [event];
499
+ const first = inputs[0];
500
+ const turnWaitMs = first.queuedAt === undefined
501
+ ? undefined
502
+ : Math.max(0, (first.selectedAt ?? first.at) - first.queuedAt);
503
+ reporter.updateSpan(parent, {
504
+ input: sessions.get(id)?.hasInput ? undefined : input,
505
+ metadata: {
506
+ taskId: event.taskId,
507
+ waitMs: turnWaitMs,
508
+ a2aMessageIds: inputs.map((input) => input.messageId),
509
+ nativeMessageIds: inputs.map((input) => input.nativeMessageId),
510
+ },
511
+ });
512
+ const prepare = reporter.startSpan(parent, {
513
+ name: event.target === 'next-step' ? 'inbox:steering' : 'inbox:prepare',
514
+ input,
515
+ startTime: new Date(event.selectedAt ?? event.at),
516
+ metadata: {
517
+ ...metadata,
518
+ waitMs,
519
+ prepareMs: event.selectedAt === undefined ? undefined : Math.max(0, event.at - event.selectedAt),
520
+ },
521
+ });
522
+ reporter.endSpan(prepare, {
523
+ output,
524
+ level: event.phase === 'prepare-failed'
525
+ ? 'ERROR'
526
+ : event.phase === 'prepare-aborted'
527
+ ? 'WARNING'
528
+ : 'DEFAULT',
529
+ }, new Date(event.at));
530
+ return;
531
+ }
532
+ const tasks = event.taskId
533
+ ? [
534
+ ['completed', 'failed', 'canceled'].includes(event.phase)
535
+ ? inboxTasks.get(inboxKey(id, event.taskId))
536
+ : taskTrace(event),
537
+ ].filter((task) => !!task)
538
+ : [...inboxTasks.values()].filter((task) => task.contextId === id &&
539
+ !task.end &&
540
+ (event.taskIds === undefined || event.taskIds.includes(task.taskId)));
541
+ for (const task of tasks) {
542
+ if (task.end)
543
+ continue;
544
+ const details = { ...metadata, taskId: task.taskId };
545
+ const operation = reporter.startSpan(task.trace, {
546
+ name: `inbox:${event.phase}`,
547
+ input,
548
+ startTime: new Date(event.at),
549
+ metadata: details,
550
+ });
551
+ reporter.endSpan(operation, { output }, new Date(event.at));
552
+ const phase = event.phase === 'cleared' ? 'canceled' : event.phase;
553
+ if (['completed', 'failed', 'canceled'].includes(phase)) {
554
+ task.end = {
555
+ phase,
556
+ at: event.at,
557
+ output: { ...output, state: phase },
558
+ level: phase === 'failed' ? 'ERROR' : phase === 'canceled' ? 'WARNING' : 'DEFAULT',
559
+ };
560
+ closeTask(task, new Date(event.at));
561
+ }
562
+ }
563
+ if (event.phase === 'disposed')
564
+ suspendTasks(id);
565
+ if (event.phase === 'disposed' || event.phase === 'cleared') {
566
+ nativeInboxes.delete(id);
567
+ inboxInputs.delete(id);
568
+ }
569
+ }
570
+ catch (error) {
571
+ console.error('[dsh-langfuse] inbox reporting failed:', error);
572
+ }
573
+ });
331
574
  // ------------------------------------------------------------------
332
575
  // Subagent child sessions: session/created precedes both subagent/start
333
576
  // and the child's first generation, and the durable header already links
@@ -385,8 +628,8 @@ export function apply(ctx, config) {
385
628
  }, endedAt));
386
629
  });
387
630
  // ------------------------------------------------------------------
388
- // Trace lifecycle: one trace per turn (top-level sessions only — child
389
- // sessions ride the parent's trace under their subagent span).
631
+ // Turn lifecycle: native tasks keep their root across turns; other sessions
632
+ // open one trace per turn. Subagents stay under their parent's trace.
390
633
  // ------------------------------------------------------------------
391
634
  ctx.on('session/event', (session, event) => {
392
635
  const sessionId = session.id;
@@ -407,7 +650,9 @@ export function apply(ctx, config) {
407
650
  if (previous?.root)
408
651
  reporter.endSpan(previous.root, { level: 'WARNING' });
409
652
  const child = children.get(sessionId);
410
- const inTurn = child ? child.turn === data.turn : sessions.get(sessionId)?.turn === data.turn;
653
+ const inTurn = child
654
+ ? child.turn === data.turn
655
+ : (sessions.get(sessionId)?.turn ?? pendingTurns.get(sessionId)?.turn) === data.turn;
411
656
  const completed = data.invocationId && !inTurn
412
657
  ? completedTurns.get(JSON.stringify([sessionId, data.turn]))
413
658
  : undefined;
@@ -553,7 +798,13 @@ export function apply(ctx, config) {
553
798
  closeHooks(sessionId, true);
554
799
  const stale = sessions.get(sessionId);
555
800
  if (stale)
556
- afterMedia(stale.trace, (endedAt) => reporter.endSpan(stale.trace, {}, endedAt));
801
+ endTurn(stale);
802
+ inboxInputs.delete(sessionId);
803
+ if (nativeInboxes.has(sessionId)) {
804
+ sessions.delete(sessionId);
805
+ pendingTurns.set(sessionId, { turn: event.data.turn, startTime: new Date(event.time) });
806
+ break;
807
+ }
557
808
  const trace = openTrace(sessionId, { turn: event.data.turn });
558
809
  if (trace)
559
810
  sessions.set(sessionId, { trace, hasInput: false, turn: event.data.turn });
@@ -575,13 +826,14 @@ export function apply(ctx, config) {
575
826
  break;
576
827
  }
577
828
  case 'turn/end': {
829
+ if (pendingTurns.has(sessionId))
830
+ traceFor(sessionId);
578
831
  const state = sessions.get(sessionId);
579
832
  if (state) {
580
833
  sessions.delete(sessionId);
581
834
  rememberTurn(sessionId, event.data.turn, state.trace, state.trace);
582
- // The turn's final answer becomes the trace's output; ending the
583
- // root span is what exports the trace (v5 exports spans on end).
584
- afterMedia(state.trace, (endedAt) => reporter.endSpan(state.trace, {
835
+ // Native task roots stay open until the A2A terminal signal.
836
+ endTurn(state, {
585
837
  output: state.lastAssistantText,
586
838
  level: event.data.reason.kind === 'error'
587
839
  ? 'ERROR'
@@ -599,7 +851,7 @@ export function apply(ctx, config) {
599
851
  ? { errorCode: event.data.reason.error.code }
600
852
  : {}),
601
853
  },
602
- }, endedAt));
854
+ });
603
855
  }
604
856
  break;
605
857
  }
@@ -611,6 +863,9 @@ export function apply(ctx, config) {
611
863
  const state = sessions.get(sessionId);
612
864
  sessions.delete(sessionId);
613
865
  traceContexts.delete(sessionId);
866
+ pendingTurns.delete(sessionId);
867
+ nativeInboxes.delete(sessionId);
868
+ inboxInputs.delete(sessionId);
614
869
  for (const [key, completed] of completedTurns) {
615
870
  if (completed.sessionId === sessionId)
616
871
  completedTurns.delete(key);
@@ -621,7 +876,8 @@ export function apply(ctx, config) {
621
876
  // trace) — v5 spans only export on end, so dropping it open would lose
622
877
  // everything it recorded.
623
878
  if (state)
624
- afterMedia(state.trace, (endedAt) => reporter.endSpan(state.trace, {}, endedAt));
879
+ endTurn(state);
880
+ suspendTasks(sessionId);
625
881
  // Children: keep the entry — background/continuable subagents dispose
626
882
  // their session BEFORE subagent/end fires, and the span's close must ride
627
883
  // subagent/end (disposing first would either leak the span or close it
@@ -920,10 +1176,16 @@ export function apply(ctx, config) {
920
1176
  // down — v5 spans only export on end, so unloading mid-turn (profile
921
1177
  // reload) would otherwise silently drop them.
922
1178
  for (const state of sessions.values())
923
- reporter.endSpan(state.trace, {}, endedAt);
1179
+ endTurn(state, {}, endedAt);
1180
+ suspendTasks();
1181
+ await drainMedia();
924
1182
  for (const child of children.values())
925
1183
  reporter.endSpan(child.span, {}, endedAt);
926
1184
  sessions.clear();
1185
+ pendingTurns.clear();
1186
+ nativeInboxes.clear();
1187
+ inboxInputs.clear();
1188
+ inboxTasks.clear();
927
1189
  completedTurns.clear();
928
1190
  traceContexts.clear();
929
1191
  children.clear();
@@ -106,7 +106,11 @@ export declare class LangfuseReporter {
106
106
  sessionId?: string;
107
107
  metadata?: Record<string, unknown>;
108
108
  context?: LangfuseTraceContext;
109
+ startTime?: Date;
110
+ traceId?: string;
109
111
  }): LangfuseSpan | null;
112
+ /** A running task keeps its trace identity; late host context is a link. */
113
+ updateContext(span: Observation | null, context: LangfuseTraceContext): void;
110
114
  /** Open a generation (one per LLM call) under any observation parent. */
111
115
  startGeneration(parent: Observation | null, input: {
112
116
  name: string;
@@ -130,6 +134,7 @@ export declare class LangfuseReporter {
130
134
  input?: unknown;
131
135
  metadata?: Record<string, unknown>;
132
136
  asType?: 'tool';
137
+ startTime?: Date;
133
138
  }): LangfuseSpan | null;
134
139
  /**
135
140
  * Merge fields into an open span — subagent enrichment (label, provider),
@@ -16,6 +16,8 @@
16
16
  * (`turn/start` opens a root span — in the v5 SDK the root observation IS
17
17
  * the trace — the turn's first `user/message` sets the input, `turn/end`
18
18
  * stamps the end reason and ends the root, which is what exports it).
19
+ * - Native A2A inbox tasks own a trace spanning admission through task termination;
20
+ * agent turns and queue controls are children of that task.
19
21
  * - subagent child sessions (`session/created` with `header.parentSession`)
20
22
  * ride the parent's trace: a `subagent` **span** opened under the enclosing
21
23
  * delegation tool span, the child's generations/tool spans nested under it,
@@ -33,8 +35,29 @@ import Schema from '@deepseek-ai/schemastery';
33
35
  import { type LangfuseTraceContext } from './client.js';
34
36
  export type { LangfuseTraceContext } from './client.js';
35
37
  export { appendHookEvent, type HookInvoked, type HookResult } from './hooks.js';
38
+ /** Optional protocol telemetry; no dependency on the A2A plugin. */
39
+ interface A2aInboxEvent {
40
+ phase: string;
41
+ contextId: string;
42
+ at: number;
43
+ pending: number;
44
+ target?: 'next-turn' | 'next-step';
45
+ input?: string;
46
+ position?: number;
47
+ agentStatus?: string;
48
+ taskId?: string;
49
+ taskIds?: string[];
50
+ messageId?: string;
51
+ referenceTaskIds?: string[];
52
+ nativeMessageId?: string;
53
+ queuedAt?: number;
54
+ selectedAt?: number;
55
+ turn?: number;
56
+ step?: number;
57
+ }
36
58
  declare module '@deepseek-ai/cordis' {
37
59
  interface Events {
60
+ 'a2a/inbox'(detail: A2aInboxEvent): void;
38
61
  /** Bind standard labels/parent context before a session's next trace starts. */
39
62
  'langfuse/context': (sessionId: string, context: LangfuseTraceContext) => void;
40
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/dsh-langfuse",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Langfuse observability plugin for DeepSeek Harness: one generation per LLM call with a nested llm-request span, one span per tool call, one trace per session turn, subagent child sessions nested under the parent's tree.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -66,7 +66,7 @@
66
66
  "devDependencies": {
67
67
  "@deepseek-ai/cordis": "^4.0.4",
68
68
  "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
69
- "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
69
+ "@deepseek-ai/dsh-attachment": "0.2.1-rc.2",
70
70
  "@deepseek-ai/dsh-hook-protocol": "0.2.0-rc.2",
71
71
  "@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
72
72
  "@deepseek-ai/dsh-llm": "0.2.0-rc.2",