@amaster.ai/dsh-langfuse 0.1.16 → 0.1.18

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/media.js ADDED
@@ -0,0 +1,57 @@
1
+ /** Display-only projection. The attachment service owns byte/digest validation. */
2
+ export async function displayBlocks(content, attachments, options, signal, budget = { remaining: options.maxBytes }) {
3
+ return Promise.all(content.map(async (block) => {
4
+ if (block.type !== 'image')
5
+ return block;
6
+ const ref = block.attachment;
7
+ const fallback = {
8
+ type: 'image',
9
+ mediaType: ref?.mediaType,
10
+ bytes: ref?.bytes,
11
+ width: ref?.width,
12
+ height: ref?.height,
13
+ offloaded: block.offloaded,
14
+ };
15
+ if (!options.enabled ||
16
+ !attachments ||
17
+ block.offloaded ||
18
+ !ref ||
19
+ !['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(ref.mediaType) ||
20
+ !Number.isSafeInteger(ref.bytes) ||
21
+ ref.bytes <= 0 ||
22
+ ref.bytes > budget.remaining)
23
+ return fallback;
24
+ budget.remaining -= ref.bytes;
25
+ const controller = new AbortController();
26
+ const timer = setTimeout(() => controller.abort(new Error('image display timed out')), options.timeoutMs);
27
+ timer.unref();
28
+ const readSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
29
+ let onAbort;
30
+ try {
31
+ readSignal.throwIfAborted();
32
+ const aborted = new Promise((_, reject) => {
33
+ onAbort = () => reject(readSignal.reason);
34
+ readSignal.addEventListener('abort', onAbort, { once: true });
35
+ });
36
+ const { data } = await Promise.race([attachments.readImage(ref, readSignal), aborted]);
37
+ readSignal.throwIfAborted();
38
+ if (data.byteLength !== ref.bytes)
39
+ throw new Error('image byte length does not match its reference');
40
+ return {
41
+ type: 'image_url',
42
+ image_url: {
43
+ url: `data:${ref.mediaType};base64,${Buffer.from(data).toString('base64')}`,
44
+ },
45
+ };
46
+ }
47
+ catch (error) {
48
+ console.error('[dsh-langfuse] image display failed:', error);
49
+ return fallback;
50
+ }
51
+ finally {
52
+ clearTimeout(timer);
53
+ if (onAbort)
54
+ readSignal.removeEventListener('abort', onAbort);
55
+ }
56
+ }));
57
+ }
@@ -39,8 +39,21 @@ export interface LangfuseConnectionConfig {
39
39
  publicKey: string;
40
40
  secretKey: string;
41
41
  baseUrl: string;
42
+ redactFields?: string[];
43
+ environment?: string;
44
+ release?: string;
45
+ userId?: string;
46
+ tags?: string[];
47
+ captureContent?: boolean;
48
+ captureMedia?: boolean;
42
49
  }
43
50
  export type ObservationLevel = 'DEFAULT' | 'WARNING' | 'ERROR';
51
+ /** Standard request context supplied by a host for one session's next trace. */
52
+ export interface LangfuseTraceContext {
53
+ userId?: string;
54
+ tags?: string[];
55
+ traceparent?: string;
56
+ }
44
57
  /** Any observation that can parent a span (trace root span or nested span/generation). */
45
58
  type Observation = LangfuseSpan | LangfuseGeneration;
46
59
  /**
@@ -52,12 +65,13 @@ type Observation = LangfuseSpan | LangfuseGeneration;
52
65
  * INCLUDES reasoning (verified in dsh-llm-deepseek@0.1.6-alpha.2:
53
66
  * `outputTokens: usage.completion_tokens`, with reasoning split out of
54
67
  * `completion_tokens_details`), so the `output` bucket subtracts
55
- * `reasoningTokens` into `output_reasoning`. Only `usageDetails` is sent.
68
+ * `reasoningTokens` into `output_reasoning_tokens`. Only `usageDetails` is sent.
56
69
  */
57
70
  export declare function usageOf(usage: TokenUsage): Record<string, number>;
58
71
  export declare class LangfuseReporter {
59
72
  private config;
60
73
  private tracing;
74
+ private api;
61
75
  private provider;
62
76
  /**
63
77
  * Observation → its trace's correlating attributes; children inherit their
@@ -76,6 +90,8 @@ export declare class LangfuseReporter {
76
90
  /** Settles (never rejects) once the lazy SDK import+wiring finished. */
77
91
  readonly ready: Promise<void>;
78
92
  constructor(config: LangfuseConnectionConfig);
93
+ /** Clone telemetry only; never mutate harness-owned messages or tool results. */
94
+ mask<T>(data: T): T;
79
95
  /** Dynamically import the SDK and wire the isolated export pipeline. */
80
96
  private init;
81
97
  /**
@@ -89,6 +105,7 @@ export declare class LangfuseReporter {
89
105
  name: string;
90
106
  sessionId?: string;
91
107
  metadata?: Record<string, unknown>;
108
+ context?: LangfuseTraceContext;
92
109
  }): LangfuseSpan | null;
93
110
  /** Open a generation (one per LLM call) under any observation parent. */
94
111
  startGeneration(parent: Observation | null, input: {
@@ -106,12 +123,13 @@ export declare class LangfuseReporter {
106
123
  level?: ObservationLevel;
107
124
  statusMessage?: string;
108
125
  metadata?: Record<string, unknown>;
109
- }): void;
126
+ }, endedAt?: Date): void;
110
127
  /** Open a span (one per tool dispatch, or a nested detail span) under any observation parent. */
111
128
  startSpan(parent: Observation | null, input: {
112
129
  name: string;
113
130
  input?: unknown;
114
131
  metadata?: Record<string, unknown>;
132
+ asType?: 'tool';
115
133
  }): LangfuseSpan | null;
116
134
  /**
117
135
  * Merge fields into an open span — subagent enrichment (label, provider),
@@ -121,7 +139,7 @@ export declare class LangfuseReporter {
121
139
  * deprecated `langfuse.trace.*` attributes (setTraceIO): older Langfuse
122
140
  * servers derive the trace row's IO from exactly those keys.
123
141
  */
124
- updateSpan(span: LangfuseSpan | null, update: {
142
+ updateSpan(span: Observation | null, update: {
125
143
  name?: string;
126
144
  input?: unknown;
127
145
  metadata?: Record<string, unknown>;
@@ -137,7 +155,7 @@ export declare class LangfuseReporter {
137
155
  level?: ObservationLevel;
138
156
  statusMessage?: string;
139
157
  metadata?: Record<string, unknown>;
140
- }): void;
158
+ }, endedAt?: Date): void;
141
159
  /** Drain buffered spans (the `session/flush` checkpoint). */
142
160
  flush(): Promise<void>;
143
161
  /** Shut the exporter down at fiber unload (provider.shutdown flushes internally). */
@@ -4,11 +4,10 @@
4
4
  * Instruments the documented seams, with zero core patches:
5
5
  *
6
6
  * - `llm/stream` waterfall → one Langfuse **generation** per LLM call
7
- * (input = full request; output, token usage and finish reason collected
7
+ * (input = loop-built options; output, token usage and finish reason collected
8
8
  * from the chunk stream), plus a nested `llm-request` **span** holding the
9
- * verbatim loop-built request and the collected chunk stream (the rawest
10
- * request/response a plugin can observe — the provider HTTP body is
11
- * assembled inside the adapter).
9
+ * same options and collected chunk stream (the rawest request/response a
10
+ * plugin can observe — the provider HTTP body is assembled inside the adapter).
12
11
  * - `tools/execute` waterfall → one Langfuse **span** per tool dispatch
13
12
  * (routed to the session's trace via `exec.agent.id` — the agent/session
14
13
  * shared identity).
@@ -31,6 +30,14 @@
31
30
  */
32
31
  import type { Context } from '@deepseek-ai/cordis';
33
32
  import Schema from '@deepseek-ai/schemastery';
33
+ import { type LangfuseTraceContext } from './client.js';
34
+ export type { LangfuseTraceContext } from './client.js';
35
+ declare module '@deepseek-ai/cordis' {
36
+ interface Events {
37
+ /** Bind standard labels/parent context before a session's next trace starts. */
38
+ 'langfuse/context': (sessionId: string, context: LangfuseTraceContext) => void;
39
+ }
40
+ }
34
41
  export declare const name = "dsh-langfuse";
35
42
  /**
36
43
  * Waterfall listeners only need the events to exist; no hard service
@@ -47,6 +54,14 @@ export declare const Config: Schema<Schemastery.ObjectS<{
47
54
  traceName: Schema<string, string>;
48
55
  /** Include full message/tool payloads in Langfuse IO. Disable to log metadata only. */
49
56
  captureContent: Schema<boolean, boolean>;
57
+ redactFields: Schema<string[], string[]>;
58
+ userId: Schema<string, string>;
59
+ tags: Schema<string[], string[]>;
60
+ environment: Schema<string, string>;
61
+ release: Schema<string, string>;
62
+ captureMedia: Schema<boolean, boolean>;
63
+ maxMediaBytes: Schema<number, number>;
64
+ mediaTimeoutMs: Schema<number, number>;
50
65
  }>, Schemastery.ObjectT<{
51
66
  enabled: Schema<boolean, boolean>;
52
67
  publicKey: Schema<string, string>;
@@ -55,6 +70,14 @@ export declare const Config: Schema<Schemastery.ObjectS<{
55
70
  traceName: Schema<string, string>;
56
71
  /** Include full message/tool payloads in Langfuse IO. Disable to log metadata only. */
57
72
  captureContent: Schema<boolean, boolean>;
73
+ redactFields: Schema<string[], string[]>;
74
+ userId: Schema<string, string>;
75
+ tags: Schema<string[], string[]>;
76
+ environment: Schema<string, string>;
77
+ release: Schema<string, string>;
78
+ captureMedia: Schema<boolean, boolean>;
79
+ maxMediaBytes: Schema<number, number>;
80
+ mediaTimeoutMs: Schema<number, number>;
58
81
  }>>;
59
82
  export interface LangfusePluginConfig {
60
83
  enabled: boolean;
@@ -63,5 +86,13 @@ export interface LangfusePluginConfig {
63
86
  baseUrl: string;
64
87
  traceName: string;
65
88
  captureContent: boolean;
89
+ redactFields?: string[];
90
+ userId?: string;
91
+ tags?: string[];
92
+ environment?: string;
93
+ release?: string;
94
+ captureMedia?: boolean;
95
+ maxMediaBytes?: number;
96
+ mediaTimeoutMs?: number;
66
97
  }
67
98
  export declare function apply(ctx: Context, config: LangfusePluginConfig): Promise<void> | void;
@@ -0,0 +1,11 @@
1
+ import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
2
+ import type { ContentBlock } from '@deepseek-ai/dsh-llm';
3
+ export interface MediaOptions {
4
+ enabled: boolean;
5
+ maxBytes: number;
6
+ timeoutMs: number;
7
+ }
8
+ /** Display-only projection. The attachment service owns byte/digest validation. */
9
+ export declare function displayBlocks(content: readonly ContentBlock[], attachments: AttachmentStore | undefined, options: MediaOptions, signal?: AbortSignal, budget?: {
10
+ remaining: number;
11
+ }): Promise<unknown[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/dsh-langfuse",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
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",
@@ -36,15 +36,23 @@
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.1",
39
+ "@deepseek-ai/dsh-attachment": ">=0.2.0-rc.1",
39
40
  "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
40
41
  "@deepseek-ai/dsh-session": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
41
42
  "@deepseek-ai/dsh-subagent": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
42
43
  "@deepseek-ai/dsh-tools": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
43
44
  "@langfuse/otel": "^5.10.1",
44
45
  "@langfuse/tracing": "^5.10.1",
46
+ "@opentelemetry/api": "^1.9.0",
45
47
  "@opentelemetry/sdk-trace-node": "^2.10.0"
46
48
  },
47
49
  "peerDependenciesMeta": {
50
+ "@opentelemetry/api": {
51
+ "optional": true
52
+ },
53
+ "@deepseek-ai/dsh-attachment": {
54
+ "optional": true
55
+ },
48
56
  "@langfuse/otel": {
49
57
  "optional": true
50
58
  },
@@ -57,17 +65,19 @@
57
65
  },
58
66
  "devDependencies": {
59
67
  "@deepseek-ai/cordis": "^4.0.4",
60
- "@deepseek-ai/dsh-agent": "0.2.0-rc.1",
61
- "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
62
- "@deepseek-ai/dsh-ptc-runtime": "0.2.0-rc.1",
63
- "@deepseek-ai/dsh-session": "0.2.0-rc.1",
64
- "@deepseek-ai/dsh-session-projection": "0.2.0-rc.1",
65
- "@deepseek-ai/dsh-subagent": "0.2.0-rc.1",
66
- "@deepseek-ai/dsh-tools": "0.2.0-rc.1",
67
- "@deepseek-ai/dsh-util-time": "0.2.0-rc.1",
68
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
69
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
70
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
71
+ "@deepseek-ai/dsh-ptc-runtime": "0.2.0-rc.2",
72
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
73
+ "@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
74
+ "@deepseek-ai/dsh-subagent": "0.2.0-rc.2",
75
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
76
+ "@deepseek-ai/dsh-util-time": "0.2.0-rc.2",
68
77
  "@langfuse/otel": "5.10.1",
69
78
  "@langfuse/tracing": "5.10.1",
70
79
  "@opentelemetry/api": "^1.9.1",
80
+ "@opentelemetry/exporter-trace-otlp-http": "0.221.0",
71
81
  "@opentelemetry/sdk-trace-node": "^2.10.0",
72
82
  "@types/node": "^22.0.0",
73
83
  "typescript": "^5.6.3",